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.api.schema.registries.synchronizers;
21  
22  
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
28  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
29  import org.apache.directory.api.ldap.model.entry.Attribute;
30  import org.apache.directory.api.ldap.model.entry.Entry;
31  import org.apache.directory.api.ldap.model.entry.Modification;
32  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
33  import org.apache.directory.api.ldap.model.entry.Value;
34  import org.apache.directory.api.ldap.model.exception.LdapException;
35  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
36  import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
37  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
38  import org.apache.directory.api.ldap.model.name.Dn;
39  import org.apache.directory.api.ldap.model.name.Rdn;
40  import org.apache.directory.api.ldap.model.schema.AttributeType;
41  import org.apache.directory.api.ldap.model.schema.SchemaManager;
42  import org.apache.directory.api.ldap.model.schema.registries.Schema;
43  import org.apache.directory.api.ldap.schema.loader.SchemaEntityFactory;
44  import org.apache.directory.api.util.Strings;
45  import org.apache.directory.server.core.api.entry.ServerEntryUtils;
46  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
47  import org.apache.directory.server.i18n.I18n;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  
52  /**
53   * This class handle modifications made on a global schema. Modifications made
54   * on SchemaObjects are handled by the specific shcemaObject synchronizers.
55   *
56   * TODO poorly implemented - revisit the SchemaChangeHandler for this puppy
57   * and do it right.
58   *
59   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
60   */
61  public class SchemaSynchronizer implements RegistrySynchronizer
62  {
63      /** A logger for this class */
64      private static final Logger LOG = LoggerFactory.getLogger( SchemaSynchronizer.class );
65  
66      private final SchemaEntityFactory factory;
67  
68      private final SchemaManager schemaManager;
69  
70      /** The m-disable AttributeType */
71      private final AttributeType disabledAT;
72  
73      /** The CN attributeType */
74      private final AttributeType cnAT;
75  
76      /** The m-dependencies AttributeType */
77      private final AttributeType dependenciesAT;
78  
79      /** A static Dn referencing ou=schema */
80      private final Dn ouSchemaDn;
81  
82  
83      /**
84       * Creates and initializes a new instance of Schema synchronizer
85       *
86       * @param schemaManager The server schemaManager
87       * @throws Exception If something went wrong
88       */
89      public SchemaSynchronizer( SchemaManager schemaManager ) throws Exception
90      {
91          this.schemaManager = schemaManager;
92          disabledAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DISABLED_AT );
93          factory = new SchemaEntityFactory();
94          cnAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.CN_AT );
95          dependenciesAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DEPENDENCIES_AT );
96  
97          ouSchemaDn = new Dn( schemaManager, SchemaConstants.OU_SCHEMA );
98      }
99  
100 
101     /**
102      * The only modification done on a schema element is on the m-disabled
103      * attributeType
104      *
105      * Depending in the existence of this attribute in the previous entry, we will
106      * have to update the entry or not.
107      */
108     @Override
109     public boolean modify( ModifyOperationContext modifyContext, Entry targetEntry, boolean cascade )
110         throws LdapException
111     {
112         Entry entry = modifyContext.getEntry();
113         List<Modification> mods = modifyContext.getModItems();
114         boolean hasModification = SCHEMA_UNCHANGED;
115 
116         // Check if the entry has a m-disabled attribute
117         Attribute disabledInEntry = entry.get( disabledAT );
118         Modification disabledModification = ServerEntryUtils.getModificationItem( mods, disabledAT );
119 
120         // The attribute might be present, but that does not mean we will change it.
121         // If it's absent, and if we have it in the previous entry, that mean we want
122         // to enable the schema
123         if ( disabledModification != null )
124         {
125             // We are trying to modify the m-disabled attribute.
126             ModificationOperation modification = disabledModification.getOperation();
127             Attribute attribute = disabledModification.getAttribute();
128 
129             hasModification = modifyDisable( modifyContext, modification, attribute, disabledInEntry );
130         }
131         else if ( disabledInEntry != null )
132         {
133             hasModification = modifyDisable( modifyContext, ModificationOperation.REMOVE_ATTRIBUTE, null,
134                 disabledInEntry );
135         }
136 
137         return hasModification;
138     }
139 
140 
141     /**
142      * {@inheritDoc}
143      */
144     @Override
145     public void moveAndRename( Dn oriChildName, Dn newParentName, Rdn newRn, boolean deleteOldRn, Entry entry,
146         boolean cascaded ) throws LdapException
147     {
148         // Not implemented yet
149     }
150 
151 
152     /**
153      * Handles the addition of a metaSchema object to the schema partition.
154      *
155      * @param entry the attributes of the new metaSchema object
156      * @throws LdapException If the add failed
157      */
158     @Override
159     public void add( Entry entry ) throws LdapException
160     {
161         Dn dn = entry.getDn();
162         Dn parentDn = dn.getParent();
163 
164         if ( !parentDn.equals( ouSchemaDn ) )
165         {
166             throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION, I18n.err( I18n.ERR_380,
167                 ouSchemaDn.getName(),
168                 parentDn.getName() ) );
169         }
170 
171         // check if the new schema is enabled or disabled
172         boolean isEnabled = false;
173         Attribute disabled = entry.get( disabledAT );
174 
175         if ( disabled == null )
176         {
177             // If the attribute is absent, then the schema is enabled by default
178             isEnabled = true;
179         }
180         else if ( !disabled.contains( "TRUE" ) )
181         {
182             isEnabled = true;
183         }
184 
185         // check to see that all dependencies are resolved and loaded if this
186         // schema is enabled, otherwise check that the dependency schemas exist
187         checkForDependencies( isEnabled, entry );
188 
189         /*
190          * There's a slight problem that may result when adding a metaSchema
191          * object if the addition of the physical entry fails.  If the schema
192          * is enabled when added in the condition tested below, that schema
193          * is added to the global registries.  We need to add this so subsequent
194          * schema entity additions are loaded into the registries as they are
195          * added to the schema partition.  However if the metaSchema object
196          * addition fails then we're left with this schema object looking like
197          * it is enabled in the registries object's schema hash.  The effects
198          * of this are unpredictable.
199          *
200          * This whole problem is due to the inability of these handlers to
201          * react to a failed operation.  To fix this we would need some way
202          * for these handlers to respond to failed operations and revert their
203          * effects on the registries.
204          *
205          * TODO: might want to add a set of failedOnXXX methods to the adapter
206          * where on failure the schema service calls the schema manager and it
207          * calls the appropriate methods on the respective handler.  This way
208          * the schema manager can rollback registry changes when LDAP operations
209          * fail.
210          */
211 
212         if ( isEnabled )
213         {
214             Schema schema = factory.getSchema( entry );
215             schemaManager.load( schema );
216         }
217     }
218 
219 
220     /**
221      * Called to react to the deletion of a metaSchema object.  This method
222      * simply removes the schema from the loaded schema map of the global
223      * registries.
224      *
225      * @param entry the attributes of the metaSchema object
226      * @param cascade If we have to process recursively
227      * @throws LdapException If the delete failed
228      */
229     @Override
230     public void delete( Entry entry, boolean cascade ) throws LdapException
231     {
232         Attribute cn = entry.get( cnAT );
233         String schemaName = cn.getString();
234 
235         // Before allowing a schema object to be deleted we must check
236         // to make sure it's not depended upon by another schema
237         Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName );
238 
239         if ( ( dependents != null ) && !dependents.isEmpty() )
240         {
241             String msg = I18n.err( I18n.ERR_381, dependents );
242             LOG.warn( msg );
243             throw new LdapUnwillingToPerformException(
244                 ResultCodeEnum.UNWILLING_TO_PERFORM,
245                 msg );
246         }
247 
248         // no need to check if schema is enabled or disabled here
249         // if not in the loaded set there will be no negative effect
250         schemaManager.unload( schemaName );
251     }
252 
253 
254     /**
255      * Responds to the rdn (commonName) of the metaSchema object being
256      * changed.  Changes all the schema entities associated with the
257      * renamed schema so they now map to a new schema name.
258      *
259      * @param entry the entry of the metaSchema object before the rename
260      * @param newRdn the new commonName of the metaSchema object
261      * @param cascade If we have to process recursively
262      * @throws LdapException If the rename failed
263      */
264     @Override
265     public void rename( Entry entry, Rdn newRdn, boolean cascade ) throws LdapException
266     {
267         String rdnAttribute = newRdn.getNormType();
268         String rdnAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName( rdnAttribute );
269 
270         if ( !rdnAttributeOid.equals( cnAT.getOid() ) )
271         {
272             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
273                 I18n.err( I18n.ERR_382, rdnAttribute ) );
274         }
275 
276         /*
277          * This operation has to do the following:
278          *
279          * [1] check and make sure there are no dependent schemas on the
280          *     one being renamed - if so an exception should result
281          *
282          * [2] make non-schema object registries modify the mapping
283          *     for their entities: non-schema object registries contain
284          *     objects that are not SchemaObjects and hence do not carry
285          *     their schema within the object as a property
286          *
287          * [3] make schema object registries do the same but the way
288          *     they do them will be different since these objects will
289          *     need to be replaced or will require a setter for the
290          *     schema name
291          */
292 
293         // step [1]
294         /*
295         String schemaName = getSchemaName( entry.getDn() );
296         Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName );
297         if ( ! dependents.isEmpty() )
298         {
299             throw new LdapUnwillingToPerformException(
300                 "Cannot allow a rename on " + schemaName + " schema while it has depentents.",
301                 ResultCodeEnum.UNWILLING_TO_PERFORM );
302         }
303 
304         // check if the new schema is enabled or disabled
305         boolean isEnabled = false;
306         EntryAttribute disabled = entry.get( disabledAT );
307 
308         if ( disabled == null )
309         {
310             isEnabled = true;
311         }
312         else if ( ! disabled.get().equals( "TRUE" ) )
313         {
314             isEnabled = true;
315         }
316 
317         if ( ! isEnabled )
318         {
319             return;
320         }
321 
322         // do steps 2 and 3 if the schema has been enabled and is loaded
323 
324         // step [2]
325         String newSchemaName = ( String ) newRdn.getUpValue();
326         registries.getComparatorRegistry().renameSchema( schemaName, newSchemaName );
327         registries.getNormalizerRegistry().renameSchema( schemaName, newSchemaName );
328         registries.getSyntaxCheckerRegistry().renameSchema( schemaName, newSchemaName );
329 
330         // step [3]
331         renameSchema( registries.getAttributeTypeRegistry(), schemaName, newSchemaName );
332         renameSchema( registries.getDitContentRuleRegistry(), schemaName, newSchemaName );
333         renameSchema( registries.getDitStructureRuleRegistry(), schemaName, newSchemaName );
334         renameSchema( registries.getMatchingRuleRegistry(), schemaName, newSchemaName );
335         renameSchema( registries.getMatchingRuleUseRegistry(), schemaName, newSchemaName );
336         renameSchema( registries.getNameFormRegistry(), schemaName, newSchemaName );
337         renameSchema( registries.getObjectClassRegistry(), schemaName, newSchemaName );
338         renameSchema( registries.getLdapSyntaxRegistry(), schemaName, newSchemaName );
339         */
340     }
341 
342 
343     /**
344      * Moves are not allowed for metaSchema objects so this always throws an
345      * UNWILLING_TO_PERFORM LdapException.
346      * 
347      * @param oriChildName The original child name
348      * @param newParentName The new parent name
349      * @param newRdn the new commonName of the metaSchema object
350      * @param deleteOldRdn If we need to delete the old Rdn 
351      * @param entry the entry of the metaSchema object before the rename
352      * @param cascade If we have to process recursively
353      * @throws LdapUnwillingToPerformException If the rename failed
354      */
355     public void moveAndRename( Dn oriChildName, Dn newParentName, String newRdn, boolean deleteOldRdn,
356         Entry entry, boolean cascade ) throws LdapUnwillingToPerformException
357     {
358         throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
359             I18n.err( I18n.ERR_383 ) );
360     }
361 
362 
363     /**
364      * Moves are not allowed for metaSchema objects so this always throws an
365      * UNWILLING_TO_PERFORM LdapException.
366      */
367     @Override
368     public void move( Dn oriChildName, Dn newParentName,
369         Entry entry, boolean cascade ) throws LdapUnwillingToPerformException
370     {
371         throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
372             I18n.err( I18n.ERR_383 ) );
373     }
374 
375 
376     // -----------------------------------------------------------------------
377     // private utility methods
378     // -----------------------------------------------------------------------
379 
380     /**
381      * Modify the Disable flag (the flag can be set to true or false).
382      *
383      * We can ADD, REMOVE or MODIFY this flag. The following matrix expose what will be the consequences
384      * of this operation, depending on the current state
385      *
386      * <pre>
387      *                 +-------------------+--------------------+--------------------+
388      *     op/state    |       TRUE        |       FALSE        |       ABSENT       |
389      * +-------+-------+----------------------------------------+--------------------+
390      * | ADD   | TRUE  | do nothing        | do nothing         | disable the schema |
391      * |       +-------+-------------------+--------------------+--------------------+
392      * |       | FALSE | do nothing        | do nothing         | do nothing         |
393      * +-------+-------+-------------------+--------------------+--------------------+
394      * |REMOVE | N/A   | enable the schema | do nothing         | do nothing         |
395      * +-------+-------+-------------------+--------------------+--------------------+
396      * |MODIFY | TRUE  | do nothing        | disable the schema | disable the schema |
397      * |       +-------+-------------------+--------------------+--------------------+
398      * |       | FALSE | enable the schema | do nothing         |  do nothing        |
399      * +-------+-------+-------------------+--------------------+--------------------+
400      * </pre>
401      */
402     private boolean modifyDisable( ModifyOperationContext modifyContext, ModificationOperation modOp,
403         Attribute disabledInMods, Attribute disabledInEntry ) throws LdapException
404     {
405         Dn name = modifyContext.getDn();
406 
407         switch ( modOp )
408         {
409         /*
410          * If the user is adding a new m-disabled attribute to an enabled schema,
411          * we check that the value is "TRUE" and disable that schema if so.
412          */
413             case ADD_ATTRIBUTE:
414                 if ( disabledInEntry == null && "TRUE".equalsIgnoreCase( disabledInMods.getString() ) )
415                 {
416                     return disableSchema( getSchemaName( name ) );
417                 }
418 
419                 break;
420 
421             /*
422              * If the user is removing the m-disabled attribute we check if the schema is currently
423              * disabled.  If so we enable the schema.
424              */
425             case REMOVE_ATTRIBUTE:
426                 if ( ( disabledInEntry != null ) && ( "TRUE".equalsIgnoreCase( disabledInEntry.getString() ) ) )
427                 {
428                     return enableSchema( getSchemaName( name ) );
429                 }
430 
431                 break;
432 
433             /*
434              * If the user is replacing the m-disabled attribute we check if the schema is
435              * currently disabled and enable it if the new state has it as enabled.  If the
436              * schema is not disabled we disable it if the mods set m-disabled to true.
437              */
438             case REPLACE_ATTRIBUTE:
439 
440                 boolean isCurrentlyDisabled = false;
441 
442                 if ( disabledInEntry != null )
443                 {
444                     isCurrentlyDisabled = "TRUE".equalsIgnoreCase( disabledInEntry.getString() );
445                 }
446 
447                 boolean isNewStateDisabled = false;
448 
449                 if ( disabledInMods != null )
450                 {
451                     Value val = disabledInMods.get();
452 
453                     if ( val == null )
454                     {
455                         isNewStateDisabled = false;
456                     }
457                     else
458                     {
459                         isNewStateDisabled = "TRUE".equalsIgnoreCase( val.getString() );
460                     }
461                 }
462 
463                 if ( isCurrentlyDisabled && !isNewStateDisabled )
464                 {
465                     return enableSchema( getSchemaName( name ) );
466                 }
467 
468                 if ( !isCurrentlyDisabled && isNewStateDisabled )
469                 {
470                     return disableSchema( getSchemaName( name ) );
471                 }
472 
473                 break;
474 
475             default:
476                 throw new IllegalArgumentException( I18n.err( I18n.ERR_384, modOp ) );
477         }
478 
479         return SCHEMA_UNCHANGED;
480     }
481 
482 
483     private String getSchemaName( Dn schema )
484     {
485         return schema.getRdn().getValue();
486     }
487 
488 
489     private boolean disableSchema( String schemaName ) throws LdapException
490     {
491         Schema schema = schemaManager.getLoadedSchema( schemaName );
492 
493         if ( schema == null )
494         {
495             // This is not possible. We can't enable a schema which is not loaded.
496             String msg = I18n.err( I18n.ERR_85, schemaName );
497             LOG.error( msg );
498             throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg );
499         }
500 
501         return schemaManager.disable( schemaName );
502 
503     }
504 
505 
506     /**
507      * Enabling a schema consist on switching all of its schema element to enable.
508      * We have to do it on a temporary registries.
509      */
510     private boolean enableSchema( String schemaName ) throws LdapException
511     {
512         Schema schema = schemaManager.getLoadedSchema( schemaName );
513 
514         if ( schema == null )
515         {
516             // We have to load the schema before enabling it.
517             schemaManager.loadDisabled( schemaName );
518         }
519 
520         return schemaManager.enable( schemaName );
521     }
522 
523 
524     /**
525      * Checks to make sure the dependencies either exist for disabled metaSchemas,
526      * or exist and are loaded (enabled) for enabled metaSchemas.
527      *
528      * @param isEnabled whether or not the new metaSchema is enabled
529      * @param entry the Attributes for the new metaSchema object
530      * @throws NamingException if the dependencies do not resolve or are not
531      * loaded (enabled)
532      */
533     private void checkForDependencies( boolean isEnabled, Entry entry ) throws LdapException
534     {
535         Attribute dependencies = entry.get( this.dependenciesAT );
536 
537         if ( dependencies == null )
538         {
539             return;
540         }
541 
542         if ( isEnabled )
543         {
544             // check to make sure all the dependencies are also enabled
545             Map<String, Schema> loaded = schemaManager.getRegistries().getLoadedSchemas();
546 
547             for ( Value value : dependencies )
548             {
549                 String dependency = value.getString();
550 
551                 if ( !loaded.containsKey( dependency ) )
552                 {
553                     throw new LdapUnwillingToPerformException(
554                         ResultCodeEnum.UNWILLING_TO_PERFORM,
555                         "Unwilling to perform operation on enabled schema with disabled or missing dependencies: "
556                             + dependency );
557                 }
558             }
559         }
560         else
561         {
562             for ( Value value : dependencies )
563             {
564                 String dependency = value.getString();
565 
566                 if ( schemaManager.getLoadedSchema( Strings.toLowerCaseAscii( dependency ) ) == null )
567                 {
568                     throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM,
569                         I18n.err( I18n.ERR_385, dependency ) );
570                 }
571             }
572         }
573     }
574 }