View Javadoc

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.entry.ServerAttribute;
25  import org.apache.directory.server.core.entry.ServerEntry;
26  import org.apache.directory.server.core.entry.ServerEntryUtils;
27  import org.apache.directory.server.schema.bootstrap.Schema;
28  import org.apache.directory.server.schema.registries.Registries;
29  import org.apache.directory.server.schema.registries.SchemaObjectRegistry;
30  import org.apache.directory.shared.ldap.constants.SchemaConstants;
31  import org.apache.directory.shared.ldap.entry.EntryAttribute;
32  import org.apache.directory.shared.ldap.entry.Modification;
33  import org.apache.directory.shared.ldap.entry.ModificationOperation;
34  import org.apache.directory.shared.ldap.entry.Value;
35  import org.apache.directory.shared.ldap.exception.LdapInvalidNameException;
36  import org.apache.directory.shared.ldap.exception.LdapOperationNotSupportedException;
37  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
38  import org.apache.directory.shared.ldap.name.LdapDN;
39  import org.apache.directory.shared.ldap.name.Rdn;
40  import org.apache.directory.shared.ldap.schema.AttributeType;
41  import org.apache.directory.shared.ldap.schema.SchemaObject;
42  
43  import javax.naming.NamingException;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  
50  /**
51   * Handles events where entries of objectClass metaSchema are modified.
52   *
53   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
54   * @version $Rev$, $Date$
55   */
56  public class MetaSchemaHandler implements SchemaChangeHandler
57  {
58      private final SchemaEntityFactory factory;
59      private final PartitionSchemaLoader loader;
60      private final Registries globalRegistries;
61      private final AttributeType disabledAT;
62      private final String OU_OID;
63      private final AttributeType cnAT;
64      private final AttributeType dependenciesAT;
65  
66  
67      public MetaSchemaHandler( Registries globalRegistries, PartitionSchemaLoader loader ) throws NamingException
68      {
69          this.globalRegistries = globalRegistries;
70          this.disabledAT = globalRegistries.getAttributeTypeRegistry().lookup( MetaSchemaConstants.M_DISABLED_AT );
71          this.loader = loader;
72          this.OU_OID = globalRegistries.getOidRegistry().getOid( SchemaConstants.OU_AT );
73          this.factory = new SchemaEntityFactory( globalRegistries );
74          this.cnAT = globalRegistries.getAttributeTypeRegistry().lookup( SchemaConstants.CN_AT );
75          this.dependenciesAT = globalRegistries.getAttributeTypeRegistry()
76              .lookup( MetaSchemaConstants.M_DEPENDENCIES_AT );
77      }
78  
79  
80      /**
81       * Reacts to modification of a metaSchema object.  At this point the 
82       * only considerable changes are to the m-disabled and the 
83       * m-dependencies attributes.
84       * 
85       * @param name the dn of the metaSchema object modified
86       * @param modOp the type of modification operation being performed
87       * @param mods the attribute modifications as an Attributes object
88       * @param entry the entry after the modifications have been applied
89       */
90      public void modify( LdapDN name, ModificationOperation modOp, ServerEntry mods, ServerEntry entry, 
91          ServerEntry targetEntry, boolean cascade ) throws Exception
92      {
93          EntryAttribute disabledInMods = mods.get( disabledAT );
94          
95          if ( disabledInMods != null )
96          {
97              disable( name, modOp, disabledInMods, entry.get( disabledAT ) );
98          }
99          
100         // check if the new schema is enabled or disabled
101         boolean isEnabled = false;
102         EntryAttribute disabled = targetEntry.get( this.disabledAT );
103         
104         if ( disabled == null )
105         {
106             isEnabled = true;
107         }
108         else if ( ! disabled.getString().equals( "TRUE" ) )
109         {
110             isEnabled = true;
111         }
112 
113         EntryAttribute dependencies = mods.get( dependenciesAT );
114         
115         if ( dependencies != null )
116         {
117             checkForDependencies( isEnabled, targetEntry );
118         }
119     }
120 
121 
122     /**
123      * Reacts to modification of a metaSchema object.  At this point the 
124      * only considerable changes are to the m-disabled and the 
125      * m-dependencies attributes.
126      * 
127      * @param name the dn of the metaSchema object modified
128      * @param mods the attribute modifications as an ModificationItem arry
129      * @param entry the entry after the modifications have been applied
130      */
131     public void modify( LdapDN name, List<Modification> mods, ServerEntry entry,
132         ServerEntry targetEntry, boolean cascade ) throws Exception
133     {
134         EntryAttribute disabledInEntry = entry.get( disabledAT );
135         Modification disabledModification = ServerEntryUtils.getModificationItem( mods, disabledAT );
136         
137         if ( disabledModification != null )
138         {
139             disable( name, 
140                      disabledModification.getOperation(), 
141                      (ServerAttribute)disabledModification.getAttribute(), 
142                      disabledInEntry );
143         }
144 
145         // check if the new schema is enabled or disabled
146         boolean isEnabled = false;
147         EntryAttribute disabled = targetEntry.get( disabledAT );
148         
149         if ( disabled == null )
150         {
151             isEnabled = true;
152         }
153         else if ( ! disabled.contains( "TRUE" ) )
154         {
155             isEnabled = true;
156         }
157 
158         ServerAttribute dependencies = ServerEntryUtils.getAttribute( mods, dependenciesAT );
159         
160         if ( dependencies != null )
161         {
162             checkForDependencies( isEnabled, targetEntry );
163         }
164     }
165 
166 
167     public void move( LdapDN oriChildName, LdapDN newParentName, Rdn newRn, boolean deleteOldRn, ServerEntry entry, boolean cascaded ) throws NamingException
168     {
169 
170     }
171 
172 
173     /**
174      * Handles the addition of a metaSchema object to the schema partition.
175      * 
176      * @param name the dn of the new metaSchema object
177      * @param entry the attributes of the new metaSchema object
178      */
179     public void add( LdapDN name, ServerEntry entry ) throws Exception
180     {
181         LdapDN parentDn = ( LdapDN ) name.clone();
182         parentDn.remove( parentDn.size() - 1 );
183         parentDn.normalize( globalRegistries.getAttributeTypeRegistry().getNormalizerMapping() );
184         if ( !parentDn.toNormName().equals( OU_OID + "=schema" ) )
185         {
186             throw new LdapInvalidNameException( "The parent dn of a schema should be " + OU_OID + "=schema and not: "
187                 + parentDn.toNormName(), ResultCodeEnum.NAMING_VIOLATION );
188         }
189 
190         // check if the new schema is enabled or disabled
191         boolean isEnabled = false;
192         EntryAttribute disabled = entry.get( disabledAT );
193         
194         if ( disabled == null )
195         {
196             isEnabled = true;
197         }
198         else if ( ! disabled.contains( "TRUE" ) )
199         {
200             isEnabled = true;
201         }
202         
203         // check to see that all dependencies are resolved and loaded if this
204         // schema is enabled, otherwise check that the dependency schemas exist
205         checkForDependencies( isEnabled, entry );
206         
207         /*
208          * There's a slight problem that may result when adding a metaSchema
209          * object if the addition of the physical entry fails.  If the schema
210          * is enabled when added in the condition tested below, that schema
211          * is added to the global registries.  We need to add this so subsequent
212          * schema entity additions are loaded into the registries as they are
213          * added to the schema partition.  However if the metaSchema object 
214          * addition fails then we're left with this schema object looking like
215          * it is enabled in the registries object's schema hash.  The effects
216          * of this are unpredicatable.
217          * 
218          * This whole problem is due to the inability of these handlers to 
219          * react to a failed operation.  To fix this we would need some way
220          * for these handlers to respond to failed operations and revert their
221          * effects on the registries.
222          * 
223          * TODO: might want to add a set of failedOnXXX methods to the adapter
224          * where on failure the schema service calls the schema manager and it
225          * calls the appropriate methods on the respective handler.  This way
226          * the schema manager can rollback registry changes when LDAP operations
227          * fail.
228          */
229 
230         if ( isEnabled )
231         {
232             Schema schema = factory.getSchema( entry );
233             globalRegistries.addToLoadedSet( schema );
234         }
235     }
236 
237 
238     /**
239      * Called to react to the deletion of a metaSchema object.  This method
240      * simply removes the schema from the loaded schema map of the global 
241      * registries.  
242      * 
243      * @param name the dn of the metaSchema object being deleted
244      * @param entry the attributes of the metaSchema object 
245      */
246     public void delete( LdapDN name, ServerEntry entry, boolean cascade ) throws Exception
247     {
248         EntryAttribute cn = entry.get( cnAT );
249         String schemaName = cn.getString();
250 
251         // Before allowing a schema object to be deleted we must check
252         // to make sure it's not depended upon by another schema
253         Set<String> dependents = loader.listDependentSchemaNames( schemaName );
254         if ( ! dependents.isEmpty() )
255         {
256             throw new LdapOperationNotSupportedException(
257                 "Cannot delete schema that has dependents: " + dependents,
258                 ResultCodeEnum.UNWILLING_TO_PERFORM );
259         }
260         
261         // no need to check if schema is enabled or disabled here
262         // if not in the loaded set there will be no negative effect
263         globalRegistries.removeFromLoadedSet( schemaName );
264     }
265 
266 
267 
268     /**
269      * Responds to the rdn (commonName) of the metaSchema object being 
270      * changed.  Changes all the schema entities associated with the 
271      * renamed schema so they now map to a new schema name.
272      * 
273      * @param name the dn of the metaSchema object renamed
274      * @param entry the entry of the metaSchema object before the rename
275      * @param newRdn the new commonName of the metaSchema object
276      */
277     public void rename( LdapDN name, ServerEntry entry, Rdn newRdn, boolean cascade ) throws Exception
278     {
279         String rdnAttribute = newRdn.getUpType();
280         String rdnAttributeOid = globalRegistries.getOidRegistry().getOid( rdnAttribute );
281         if ( ! rdnAttributeOid.equals( cnAT.getOid() ) )
282         {
283             throw new LdapOperationNotSupportedException( 
284                 "Cannot allow rename with rdnAttribute set to " 
285                 + rdnAttribute + ": cn must be used instead." ,
286                 ResultCodeEnum.UNWILLING_TO_PERFORM );
287         }
288 
289         /*
290          * This operation has to do the following:
291          * 
292          * [1] check and make sure there are no dependent schemas on the 
293          *     one being renamed - if so an exception should result
294          *      
295          * [2] make non-schema object registries modify the mapping 
296          *     for their entities: non-schema object registries contain
297          *     objects that are not SchemaObjects and hence do not carry
298          *     their schema within the object as a property
299          *     
300          * [3] make schema object registries do the same but the way
301          *     they do them will be different since these objects will
302          *     need to be replaced or will require a setter for the 
303          *     schema name
304          */
305         
306         // step [1]
307         String schemaName = getSchemaName( name );
308         Set<String> dependents = loader.listDependentSchemaNames( schemaName );
309         if ( ! dependents.isEmpty() )
310         {
311             throw new LdapOperationNotSupportedException( 
312                 "Cannot allow a rename on " + schemaName + " schema while it has depentents.",
313                 ResultCodeEnum.UNWILLING_TO_PERFORM );
314         }
315 
316         // check if the new schema is enabled or disabled
317         boolean isEnabled = false;
318         EntryAttribute disabled = entry.get( disabledAT );
319         
320         if ( disabled == null )
321         {
322             isEnabled = true;
323         }
324         else if ( ! disabled.get().equals( "TRUE" ) )
325         {
326             isEnabled = true;
327         }
328 
329         if ( ! isEnabled )
330         {
331             return;
332         }
333 
334         // do steps 2 and 3 if the schema has been enabled and is loaded
335         
336         // step [2] 
337         String newSchemaName = ( String ) newRdn.getUpValue();
338         globalRegistries.getComparatorRegistry().renameSchema( schemaName, newSchemaName );
339         globalRegistries.getNormalizerRegistry().renameSchema( schemaName, newSchemaName );
340         globalRegistries.getSyntaxCheckerRegistry().renameSchema( schemaName, newSchemaName );
341         
342         // step [3]
343         renameSchema( globalRegistries.getAttributeTypeRegistry(), schemaName, newSchemaName );
344         renameSchema( globalRegistries.getDitContentRuleRegistry(), schemaName, newSchemaName );
345         renameSchema( globalRegistries.getDitStructureRuleRegistry(), schemaName, newSchemaName );
346         renameSchema( globalRegistries.getMatchingRuleRegistry(), schemaName, newSchemaName );
347         renameSchema( globalRegistries.getMatchingRuleUseRegistry(), schemaName, newSchemaName );
348         renameSchema( globalRegistries.getNameFormRegistry(), schemaName, newSchemaName );
349         renameSchema( globalRegistries.getObjectClassRegistry(), schemaName, newSchemaName );
350         renameSchema( globalRegistries.getSyntaxRegistry(), schemaName, newSchemaName );
351     }
352     
353 
354     /**
355      * Moves are not allowed for metaSchema objects so this always throws an
356      * UNWILLING_TO_PERFORM LdapException.
357      */
358     public void move( LdapDN oriChildName, LdapDN newParentName, String newRn, boolean deleteOldRn, 
359         ServerEntry entry, boolean cascade ) throws NamingException
360     {
361         throw new LdapOperationNotSupportedException( "Moving around schemas is not allowed.",
362             ResultCodeEnum.UNWILLING_TO_PERFORM );
363     }
364 
365 
366     /**
367      * Moves are not allowed for metaSchema objects so this always throws an
368      * UNWILLING_TO_PERFORM LdapException.
369      */
370     public void replace( LdapDN oriChildName, LdapDN newParentName, 
371         ServerEntry entry, boolean cascade ) throws NamingException
372     {
373         throw new LdapOperationNotSupportedException( "Moving around schemas is not allowed.",
374             ResultCodeEnum.UNWILLING_TO_PERFORM );
375     }
376 
377     
378     // -----------------------------------------------------------------------
379     // private utility methods
380     // -----------------------------------------------------------------------
381 
382     
383     private void disable( LdapDN name, ModificationOperation modOp, EntryAttribute disabledInMods, EntryAttribute disabledInEntry )
384         throws Exception
385     {
386         switch ( modOp )
387         {
388             /*
389              * If the user is adding a new m-disabled attribute to an enabled schema, 
390              * we check that the value is "TRUE" and disable that schema if so.
391              */
392             case ADD_ATTRIBUTE :
393                 if ( disabledInEntry == null )
394                 {
395                     if ( "TRUE".equalsIgnoreCase( disabledInMods.getString() ) )
396                     {
397                         disableSchema( getSchemaName( name ) );
398                     }
399                 }
400                 
401                 break;
402 
403             /*
404              * If the user is removing the m-disabled attribute we check if the schema is currently 
405              * disabled.  If so we enable the schema.
406              */
407             case REMOVE_ATTRIBUTE :
408                 if ( "TRUE".equalsIgnoreCase( disabledInEntry.getString() ) )
409                 {
410                     enableSchema( getSchemaName( name ) );
411                 }
412                 
413                 break;
414 
415             /*
416              * If the user is replacing the m-disabled attribute we check if the schema is 
417              * currently disabled and enable it if the new state has it as enabled.  If the
418              * schema is not disabled we disable it if the mods set m-disabled to true.
419              */
420             case REPLACE_ATTRIBUTE :
421                 boolean isCurrentlyDisabled = "TRUE".equalsIgnoreCase( disabledInEntry.getString() );
422                 boolean isNewStateDisabled = "TRUE".equalsIgnoreCase( disabledInMods.getString() );
423 
424                 if ( isCurrentlyDisabled && !isNewStateDisabled )
425                 {
426                     enableSchema( getSchemaName( name ) );
427                     break;
428                 }
429 
430                 if ( !isCurrentlyDisabled && isNewStateDisabled )
431                 {
432                     disableSchema( getSchemaName( name ) );
433                 }
434                 
435                 break;
436                 
437             default:
438                 throw new IllegalArgumentException( "Unknown modify operation type: " + modOp );
439         }
440     }
441 
442 
443     private String getSchemaName( LdapDN schema )
444     {
445         return ( String ) schema.getRdn().getValue();
446     }
447 
448 
449     private void disableSchema( String schemaName ) throws Exception
450     {
451         Set<String> dependents = loader.listEnabledDependentSchemaNames( schemaName );
452         if ( ! dependents.isEmpty() )
453         {
454             throw new LdapOperationNotSupportedException(
455                 "Cannot disable schema with enabled dependents: " + dependents,
456                 ResultCodeEnum.UNWILLING_TO_PERFORM );
457         }
458         
459         globalRegistries.unload( schemaName );
460     }
461 
462 
463     /**
464      * TODO - for now we're just going to add the schema to the global 
465      * registries ... we may need to add it to more than that though later.
466      */
467     private void enableSchema( String schemaName ) throws Exception
468     {
469         if ( globalRegistries.getLoadedSchemas().containsKey( schemaName ) )
470         {
471             // TODO log warning: schemaName + " was already loaded"
472             return;
473         }
474 
475         Schema schema = loader.getSchema( schemaName );
476         loader.loadWithDependencies( schema, globalRegistries );
477     }
478 
479 
480     /**
481      * Checks to make sure the dependencies either exist for disabled metaSchemas,
482      * or exist and are loaded (enabled) for enabled metaSchemas.
483      * 
484      * @param isEnabled whether or not the new metaSchema is enabled
485      * @param entry the Attributes for the new metaSchema object
486      * @throws NamingException if the dependencies do not resolve or are not
487      * loaded (enabled)
488      */
489     private void checkForDependencies( boolean isEnabled, ServerEntry entry ) throws Exception
490     {
491         EntryAttribute dependencies = entry.get( this.dependenciesAT );
492 
493         if ( dependencies == null )
494         {
495             return;
496         }
497         
498         if ( isEnabled )
499         {
500             // check to make sure all the dependencies are also enabled
501             Map<String,Schema> loaded = globalRegistries.getLoadedSchemas();
502             
503             for ( Value<?> value:dependencies )
504             {
505                 String dependency = ( String ) value.get();
506                 
507                 if ( ! loaded.containsKey( dependency ) )
508                 {
509                     throw new LdapOperationNotSupportedException( 
510                         "Unwilling to perform operation on enabled schema with disabled or missing dependencies: " 
511                         + dependency, ResultCodeEnum.UNWILLING_TO_PERFORM );
512                 }
513             }
514         }
515         else
516         {
517             Set<String> allSchemas = loader.getSchemaNames();
518             
519             for ( Value<?> value:dependencies )
520             {
521                 String dependency = ( String ) value.get();
522                 
523                 if ( ! allSchemas.contains( dependency ) )
524                 {
525                     throw new LdapOperationNotSupportedException( 
526                         "Unwilling to perform operation on schema with missing dependencies: " + dependency, 
527                         ResultCodeEnum.UNWILLING_TO_PERFORM );
528                 }
529             }
530         }
531     }
532 
533     
534     /**
535      * Used to iterate through SchemaObjects in a SchemaObjectRegistry and rename
536      * their schema property to a new schema name.
537      * 
538      * @param registry the registry whose objects are changed
539      * @param originalSchemaName the original schema name
540      * @param newSchemaName the new schema name
541      */
542     private void renameSchema( SchemaObjectRegistry registry, String originalSchemaName, String newSchemaName ) 
543     {
544         Iterator<? extends SchemaObject> list = registry.iterator();
545         while ( list.hasNext() )
546         {
547             SchemaObject obj = list.next();
548             if ( obj.getSchema().equalsIgnoreCase( originalSchemaName ) )
549             {
550                 obj.setSchema( newSchemaName );
551             }
552         }
553     }
554 }