1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.schema;
21  
22  
23  import org.apache.directory.server.constants.MetaSchemaConstants;
24  import org.apache.directory.server.core.DirectoryService;
25  import org.apache.directory.server.core.integ.CiRunner;
26  import static org.apache.directory.server.core.integ.IntegrationUtils.getRootContext;
27  import static org.apache.directory.server.core.integ.IntegrationUtils.getSchemaContext;
28  import org.apache.directory.server.schema.registries.MatchingRuleRegistry;
29  import org.apache.directory.server.schema.registries.SyntaxRegistry;
30  import org.apache.directory.shared.ldap.constants.SchemaConstants;
31  import org.apache.directory.shared.ldap.exception.LdapInvalidNameException;
32  import org.apache.directory.shared.ldap.exception.LdapOperationNotSupportedException;
33  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
34  import org.apache.directory.shared.ldap.name.LdapDN;
35  import org.apache.directory.shared.ldap.schema.Syntax;
36  import org.apache.directory.shared.ldap.schema.syntax.AcceptAllSyntaxChecker;
37  import static org.junit.Assert.assertEquals;
38  import static org.junit.Assert.assertTrue;
39  import static org.junit.Assert.assertFalse;
40  import static org.junit.Assert.fail;
41  import org.junit.Test;
42  import org.junit.runner.RunWith;
43  
44  import javax.naming.NamingEnumeration;
45  import javax.naming.NamingException;
46  import javax.naming.directory.Attribute;
47  import javax.naming.directory.Attributes;
48  import javax.naming.directory.BasicAttribute;
49  import javax.naming.directory.BasicAttributes;
50  import javax.naming.directory.DirContext;
51  import javax.naming.directory.ModificationItem;
52  import javax.naming.directory.SearchControls;
53  import javax.naming.directory.SearchResult;
54  
55  import java.util.ArrayList;
56  import java.util.List;
57  
58  
59  /**
60   * A test case which tests the addition of various schema elements
61   * to the ldap server.
62   *
63   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64   * @version $Rev$
65   */
66  @RunWith ( CiRunner.class )
67  public class MetaSyntaxHandlerIT
68  {
69      private static final String DESCRIPTION0 = "A test normalizer";
70      private static final String DESCRIPTION1 = "An alternate description";
71  
72      private static final String OID = "1.3.6.1.4.1.18060.0.4.0.0.100000";
73      private static final String NEW_OID = "1.3.6.1.4.1.18060.0.4.0.0.100001";
74  
75      private static final String MR_OID = "1.3.6.1.4.1.18060.0.4.0.1.100000";
76      private static final String MR_DESCRIPTION = "A test matchingRule";
77  
78      private static final String SUBSCHEMA_SUBENTRY = "subschemaSubentry";
79  
80  
81      public static DirectoryService service;
82  
83  
84      private static SyntaxRegistry getSyntaxRegistry()
85      {
86          return service.getRegistries().getSyntaxRegistry();
87      }
88  
89  
90      private static MatchingRuleRegistry getMatchingRuleRegistry()
91      {
92          return service.getRegistries().getMatchingRuleRegistry();
93      }
94  
95      
96      /**
97       * Gets relative DN to ou=schema.
98       *
99       * @param schemaName the name of the schema
100      * @return the dn of the container for the syntax entities
101      * @throws Exception on error
102      */
103     private LdapDN getSyntaxContainer( String schemaName ) throws Exception
104     {
105         return new LdapDN( "ou=syntaxes,cn=" + schemaName );
106     }
107     
108     
109     // ----------------------------------------------------------------------
110     // Test all core methods with normal operational pathways
111     // ----------------------------------------------------------------------
112 
113     
114     @Test
115     public void testAddSyntax() throws Exception
116     {
117         Attributes attrs = new BasicAttributes( true );
118         Attribute oc = new BasicAttribute( SchemaConstants.OBJECT_CLASS_AT, "top" );
119         oc.add( MetaSchemaConstants.META_TOP_OC );
120         oc.add( MetaSchemaConstants.META_SYNTAX_OC );
121         attrs.put( oc );
122         attrs.put( MetaSchemaConstants.M_OID_AT, OID );
123         attrs.put( MetaSchemaConstants.M_DESCRIPTION_AT, DESCRIPTION0 );
124         
125         LdapDN dn = getSyntaxContainer( "apachemeta" );
126         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
127         createDummySyntaxChecker( OID, "apachemeta" );
128         getSchemaContext( service ).createSubcontext( dn, attrs );
129         
130         assertTrue( getSyntaxRegistry().hasSyntax( OID ) );
131         assertEquals( getSyntaxRegistry().getSchemaName( OID ), "apachemeta" );
132     }
133     
134     
135     @Test
136     public void testDeleteSyntax() throws Exception
137     {
138         LdapDN dn = getSyntaxContainer( "apachemeta" );
139         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
140         testAddSyntax();
141         
142         getSchemaContext( service ).destroySubcontext( dn );
143 
144         assertFalse( "syntax should be removed from the registry after being deleted", 
145             getSyntaxRegistry().hasSyntax( OID ) );
146 
147         //noinspection EmptyCatchBlock
148         try
149         {
150             getSyntaxRegistry().lookup( OID );
151             fail( "syntax lookup should fail after deleting it" );
152         }
153         catch( NamingException e )
154         {
155         }
156     }
157 
158 
159     @Test
160     public void testRenameSyntax() throws Exception
161     {
162         LdapDN dn = getSyntaxContainer( "apachemeta" );
163         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
164         testAddSyntax();
165         
166         LdapDN newdn = getSyntaxContainer( "apachemeta" );
167         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + NEW_OID );
168         getSchemaContext( service ).rename( dn, newdn );
169 
170         assertFalse( "old syntax OID should be removed from the registry after being renamed", 
171             getSyntaxRegistry().hasSyntax( OID ) );
172 
173         //noinspection EmptyCatchBlock
174         try
175         {
176             getSyntaxRegistry().lookup( OID );
177             fail( "syntax lookup should fail after deleting the syntax" );
178         }
179         catch( NamingException e )
180         {
181         }
182 
183         assertTrue( getSyntaxRegistry().hasSyntax( NEW_OID ) );
184     }
185 
186 
187     @Test
188     public void testMoveSyntax() throws Exception
189     {
190         testAddSyntax();
191         
192         LdapDN dn = getSyntaxContainer( "apachemeta" );
193         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
194 
195         LdapDN newdn = getSyntaxContainer( "apache" );
196         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
197         
198         getSchemaContext( service ).rename( dn, newdn );
199 
200         assertTrue( "syntax OID should still be present", 
201             getSyntaxRegistry().hasSyntax( OID ) );
202         
203         assertEquals( "syntax schema should be set to apache not apachemeta", 
204             getSyntaxRegistry().getSchemaName( OID ), "apache" );
205     }
206 
207 
208     @Test
209     public void testMoveSyntaxAndChangeRdn() throws Exception
210     {
211         testAddSyntax();
212         
213         LdapDN dn = getSyntaxContainer( "apachemeta" );
214         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
215 
216         LdapDN newdn = getSyntaxContainer( "apache" );
217         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + NEW_OID );
218         
219         getSchemaContext( service ).rename( dn, newdn );
220 
221         assertFalse( "old syntax OID should NOT be present", 
222             getSyntaxRegistry().hasSyntax( OID ) );
223         
224         assertTrue( "new syntax OID should be present", 
225             getSyntaxRegistry().hasSyntax( NEW_OID ) );
226         
227         assertEquals( "syntax with new oid should have schema set to apache NOT apachemeta", 
228             getSyntaxRegistry().getSchemaName( NEW_OID ), "apache" );
229     }
230 
231     
232     @Test
233     public void testModifySyntaxWithModificationItems() throws Exception
234     {
235         testAddSyntax();
236         
237         Syntax syntax = getSyntaxRegistry().lookup( OID );
238         assertEquals( syntax.getDescription(), DESCRIPTION0 );
239 
240         LdapDN dn = getSyntaxContainer( "apachemeta" );
241         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
242         
243         ModificationItem[] mods = new ModificationItem[1];
244         Attribute attr = new BasicAttribute( MetaSchemaConstants.M_DESCRIPTION_AT, DESCRIPTION1 );
245         mods[0] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, attr );
246         getSchemaContext( service ).modifyAttributes( dn, mods );
247 
248         assertTrue( "syntax OID should still be present", 
249             getSyntaxRegistry().hasSyntax( OID ) );
250         
251         assertEquals( "syntax schema should be set to apachemeta", 
252             getSyntaxRegistry().getSchemaName( OID ), "apachemeta" );
253         
254         syntax = getSyntaxRegistry().lookup( OID );
255         assertEquals( syntax.getDescription(), DESCRIPTION1 );
256     }
257 
258     
259     @Test
260     public void testModifySyntaxWithAttributes() throws Exception
261     {
262         testAddSyntax();
263         
264         Syntax syntax = getSyntaxRegistry().lookup( OID );
265         assertEquals( syntax.getDescription(), DESCRIPTION0 );
266 
267         LdapDN dn = getSyntaxContainer( "apachemeta" );
268         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
269         
270         Attributes mods = new BasicAttributes( true );
271         mods.put( MetaSchemaConstants.M_DESCRIPTION_AT, DESCRIPTION1 );
272         getSchemaContext( service ).modifyAttributes( dn, DirContext.REPLACE_ATTRIBUTE, mods );
273 
274         assertTrue( "syntax OID should still be present", 
275             getSyntaxRegistry().hasSyntax( OID ) );
276         
277         assertEquals( "syntax schema should be set to apachemeta", 
278             getSyntaxRegistry().getSchemaName( OID ), "apachemeta" );
279 
280         syntax = getSyntaxRegistry().lookup( OID );
281         assertEquals( syntax.getDescription(), DESCRIPTION1 );
282     }
283     
284 
285     // ----------------------------------------------------------------------
286     // Test move, rename, and delete when a MR exists and uses the Normalizer
287     // ----------------------------------------------------------------------
288 
289     
290     @Test
291     public void testDeleteSyntaxWhenInUse() throws Exception
292     {
293         LdapDN dn = getSyntaxContainer( "apachemeta" );
294         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
295         testAddSyntax();
296         addDependeeMatchingRule( OID );
297         
298         try
299         {
300             getSchemaContext( service ).destroySubcontext( dn );
301             fail( "should not be able to delete a syntax in use" );
302         }
303         catch( LdapOperationNotSupportedException e ) 
304         {
305             assertEquals( e.getResultCode(), ResultCodeEnum.UNWILLING_TO_PERFORM );
306         }
307 
308         assertTrue( "syntax should still be in the registry after delete failure", 
309             getSyntaxRegistry().hasSyntax( OID ) );
310     }
311     
312     
313     @Test
314     public void testMoveSyntaxWhenInUse() throws Exception
315     {
316         testAddSyntax();
317         addDependeeMatchingRule( OID );
318         
319         LdapDN dn = getSyntaxContainer( "apachemeta" );
320         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
321 
322         LdapDN newdn = getSyntaxContainer( "apache" );
323         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
324         
325         try
326         {
327             getSchemaContext( service ).rename( dn, newdn );
328             fail( "should not be able to move a syntax in use" );
329         }
330         catch( LdapOperationNotSupportedException e ) 
331         {
332             assertEquals( e.getResultCode(), ResultCodeEnum.UNWILLING_TO_PERFORM );
333         }
334 
335         assertTrue( "syntax should still be in the registry after move failure", 
336             getSyntaxRegistry().hasSyntax( OID ) );
337     }
338 
339 
340     @Test
341     public void testMoveSyntaxAndChangeRdnWhenInUse() throws Exception
342     {
343         testAddSyntax();
344         addDependeeMatchingRule( OID );
345         
346         LdapDN dn = getSyntaxContainer( "apachemeta" );
347         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
348 
349         LdapDN newdn = getSyntaxContainer( "apache" );
350         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + NEW_OID );
351         
352         try
353         {
354             getSchemaContext( service ).rename( dn, newdn );
355             fail( "should not be able to move a syntax in use" );
356         }
357         catch( LdapOperationNotSupportedException e ) 
358         {
359             assertEquals( e.getResultCode(), ResultCodeEnum.UNWILLING_TO_PERFORM );
360         }
361 
362         assertTrue( "syntax should still be in the registry after move failure", 
363             getSyntaxRegistry().hasSyntax( OID ) );
364     }
365 
366     
367     /**
368      * Gets relative DN to ou=schema.
369      *
370      * @param schemaName the name of the schmea
371      * @return the dn of the container entry holding matchingRules
372      * @throws Exception on parse errors
373      */
374     private LdapDN getMatchingRuleContainer( String schemaName ) throws Exception
375     {
376         return new LdapDN( "ou=matchingRules,cn=" + schemaName );
377     }
378     
379     
380     private void addDependeeMatchingRule( String oid ) throws Exception
381     {
382         Attributes attrs = new BasicAttributes( true );
383         Attribute oc = new BasicAttribute( SchemaConstants.OBJECT_CLASS_AT, "top" );
384         oc.add( MetaSchemaConstants.META_TOP_OC );
385         oc.add( MetaSchemaConstants.META_MATCHING_RULE_OC );
386         attrs.put( oc );
387         attrs.put( MetaSchemaConstants.M_OID_AT, MR_OID );
388         attrs.put( MetaSchemaConstants.M_SYNTAX_AT, OID );
389         attrs.put( MetaSchemaConstants.M_DESCRIPTION_AT, MR_DESCRIPTION );
390         
391         LdapDN dn = getMatchingRuleContainer( "apachemeta" );
392         dn.add( MetaSchemaConstants.M_OID_AT + "=" + MR_OID );
393         getSchemaContext( service ).createSubcontext( dn, attrs );
394         
395         assertTrue( getMatchingRuleRegistry().hasMatchingRule( MR_OID ) );
396         assertEquals( getMatchingRuleRegistry().getSchemaName( MR_OID ), "apachemeta" );
397     }
398 
399     
400     @Test
401     public void testRenameNormalizerWhenInUse() throws Exception
402     {
403         LdapDN dn = getSyntaxContainer( "apachemeta" );
404         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
405         testAddSyntax();
406         addDependeeMatchingRule( OID );
407         
408         LdapDN newdn = getSyntaxContainer( "apachemeta" );
409         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + NEW_OID );
410         
411         try
412         {
413             getSchemaContext( service ).rename( dn, newdn );
414             fail( "should not be able to rename a syntax in use" );
415         }
416         catch( LdapOperationNotSupportedException e ) 
417         {
418             assertEquals( e.getResultCode(), ResultCodeEnum.UNWILLING_TO_PERFORM );
419         }
420 
421         assertTrue( "syntax should still be in the registry after rename failure", 
422             getSyntaxRegistry().hasSyntax( OID ) );
423     }
424 
425 
426     // ----------------------------------------------------------------------
427     // Let's try some freaky stuff
428     // ----------------------------------------------------------------------
429 
430 
431     @Test
432     public void testMoveSyntaxToTop() throws Exception
433     {
434         testAddSyntax();
435         
436         LdapDN dn = getSyntaxContainer( "apachemeta" );
437         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
438 
439         LdapDN top = new LdapDN();
440         top.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
441         
442         try
443         {
444             getSchemaContext( service ).rename( dn, top );
445             fail( "should not be able to move a syntax up to ou=schema" );
446         }
447         catch( LdapInvalidNameException e ) 
448         {
449             assertEquals( e.getResultCode(), ResultCodeEnum.NAMING_VIOLATION );
450         }
451 
452         assertTrue( "syntax should still be in the registry after move failure", 
453             getSyntaxRegistry().hasSyntax( OID ) );
454     }
455 
456 
457     @Test
458     public void testMoveSyntaxToComparatorContainer() throws Exception
459     {
460         testAddSyntax();
461         
462         LdapDN dn = getSyntaxContainer( "apachemeta" );
463         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
464 
465         LdapDN newdn = new LdapDN( "ou=comparators,cn=apachemeta" );
466         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
467         
468         try
469         {
470             getSchemaContext( service ).rename( dn, newdn );
471             fail( "should not be able to move a syntax into comparators container" );
472         }
473         catch( LdapInvalidNameException e ) 
474         {
475             assertEquals( e.getResultCode(), ResultCodeEnum.NAMING_VIOLATION );
476         }
477 
478         assertTrue( "syntax should still be in the registry after move failure", 
479             getSyntaxRegistry().hasSyntax( OID ) );
480     }
481     
482     
483     @Test
484     public void testAddSyntaxToDisabledSchema() throws Exception
485     {
486         Attributes attrs = new BasicAttributes( true );
487         Attribute oc = new BasicAttribute( SchemaConstants.OBJECT_CLASS_AT, "top" );
488         oc.add( MetaSchemaConstants.META_TOP_OC );
489         oc.add( MetaSchemaConstants.META_SYNTAX_OC );
490         attrs.put( oc );
491         attrs.put( MetaSchemaConstants.M_OID_AT, OID );
492         attrs.put( MetaSchemaConstants.M_DESCRIPTION_AT, DESCRIPTION0 );
493         
494         // nis is by default inactive
495         LdapDN dn = getSyntaxContainer( "nis" );
496         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
497         createDummySyntaxChecker( OID, "nis" );
498         getSchemaContext( service ).createSubcontext( dn, attrs );
499         
500         assertFalse( "adding new syntax to disabled schema should not register it into the registries", 
501             getSyntaxRegistry().hasSyntax( OID ) );
502     }
503 
504 
505     @Test
506     public void testMoveSyntaxToDisabledSchema() throws Exception
507     {
508         testAddSyntax();
509         
510         LdapDN dn = getSyntaxContainer( "apachemeta" );
511         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
512 
513         // nis is inactive by default
514         LdapDN newdn = getSyntaxContainer( "nis" );
515         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
516         
517         getSchemaContext( service ).rename( dn, newdn );
518 
519         assertFalse( "syntax OID should no longer be present", 
520             getSyntaxRegistry().hasSyntax( OID ) );
521     }
522 
523 
524     @Test
525     public void testMoveSyntaxToEnabledSchema() throws Exception
526     {
527         testAddSyntaxToDisabledSchema();
528         
529         // nis is inactive by default
530         LdapDN dn = getSyntaxContainer( "nis" );
531         dn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
532 
533         assertFalse( "syntax OID should NOT be present when added to disabled nis schema", 
534             getSyntaxRegistry().hasSyntax( OID ) );
535 
536         LdapDN newdn = getSyntaxContainer( "apachemeta" );
537         newdn.add( MetaSchemaConstants.M_OID_AT + "=" + OID );
538         
539         getSchemaContext( service ).rename( dn, newdn );
540 
541         assertTrue( "syntax OID should be present when moved to enabled schema", 
542             getSyntaxRegistry().hasSyntax( OID ) );
543         
544         assertEquals( "syntax should be in apachemeta schema after move", 
545             getSyntaxRegistry().getSchemaName( OID ), "apachemeta" );
546     }
547 
548 
549     private void createDummySyntaxChecker( String oid, String schema ) throws Exception
550     {
551         List<String> descriptions = new ArrayList<String>();
552         descriptions.add( "( " + oid + " DESC 'bogus desc' FQCN " + AcceptAllSyntaxChecker.class.getName() 
553             + " X-SCHEMA '" + schema + "' )" );
554         modify( DirContext.ADD_ATTRIBUTE, descriptions, "syntaxCheckers" );
555     }
556     
557     
558     private void modify( int op, List<String> descriptions, String opAttr ) throws Exception
559     {
560         LdapDN dn = new LdapDN( getSubschemaSubentryDN() );
561         Attribute attr = new BasicAttribute( opAttr );
562         for ( String description : descriptions )
563         {
564             attr.add( description );
565         }
566         
567         Attributes mods = new BasicAttributes( true );
568         mods.put( attr );
569         
570         getRootContext( service ).modifyAttributes( dn, op, mods );
571     }
572     
573     
574     /**
575      * Get's the subschemaSubentry attribute value from the rootDSE.
576      * 
577      * @return the subschemaSubentry distinguished name
578      * @throws Exception if there are problems accessing the RootDSE
579      */
580     private String getSubschemaSubentryDN() throws Exception
581     {
582         SearchControls controls = new SearchControls();
583         controls.setSearchScope( SearchControls.OBJECT_SCOPE );
584         controls.setReturningAttributes( new String[]{ SUBSCHEMA_SUBENTRY } );
585         
586         NamingEnumeration<SearchResult> results = getRootContext( service ).search(
587                 "", "(objectClass=*)", controls );
588         SearchResult result = results.next();
589         results.close();
590         Attribute subschemaSubentry = result.getAttributes().get( SUBSCHEMA_SUBENTRY );
591         return ( String ) subschemaSubentry.get();
592     }
593 }