| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.server.core.api.schema.registries.synchronizers; |
| |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.naming.NamingException; |
| |
| import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants; |
| import org.apache.directory.api.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Modification; |
| import org.apache.directory.api.ldap.model.entry.ModificationOperation; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; |
| import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; |
| import org.apache.directory.api.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.name.Rdn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.SchemaManager; |
| import org.apache.directory.api.ldap.model.schema.registries.Schema; |
| import org.apache.directory.api.ldap.schemaloader.SchemaEntityFactory; |
| import org.apache.directory.api.util.Strings; |
| import org.apache.directory.server.core.api.entry.ServerEntryUtils; |
| import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.i18n.I18n; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * This class handle modifications made on a global schema. Modifications made |
| * on SchemaObjects are handled by the specific shcemaObject synchronizers. |
| * |
| * @todo poorly implemented - revisit the SchemaChangeHandler for this puppy |
| * and do it right. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class SchemaSynchronizer implements RegistrySynchronizer |
| { |
| /** A logger for this class */ |
| private static final Logger LOG = LoggerFactory.getLogger( SchemaSynchronizer.class ); |
| |
| private final SchemaEntityFactory factory; |
| |
| private final SchemaManager schemaManager; |
| |
| /** The global registries */ |
| //private final Registries registries; |
| |
| /** The m-disable AttributeType */ |
| private final AttributeType disabledAT; |
| |
| /** The CN attributeType */ |
| private final AttributeType cnAT; |
| |
| /** The m-dependencies AttributeType */ |
| private final AttributeType dependenciesAT; |
| |
| /** A static Dn referencing ou=schema */ |
| private final Dn ouSchemaDn; |
| |
| |
| /** |
| * Creates and initializes a new instance of Schema synchronizer |
| * |
| * @param schemaManager The server schemaManager |
| * @throws Exception If something went wrong |
| */ |
| public SchemaSynchronizer( SchemaManager schemaManager ) throws Exception |
| { |
| //this.registries = schemaManager.getRegistries(); |
| this.schemaManager = schemaManager; |
| disabledAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DISABLED_AT ); |
| factory = new SchemaEntityFactory(); |
| cnAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.CN_AT ); |
| dependenciesAT = schemaManager.lookupAttributeTypeRegistry( MetaSchemaConstants.M_DEPENDENCIES_AT ); |
| |
| ouSchemaDn = new Dn( schemaManager, SchemaConstants.OU_SCHEMA ); |
| } |
| |
| |
| /** |
| * The only modification done on a schema element is on the m-disabled |
| * attributeType |
| * |
| * Depending in the existence of this attribute in the previous entry, we will |
| * have to update the entry or not. |
| */ |
| public boolean modify( ModifyOperationContext modifyContext, Entry targetEntry, boolean cascade ) |
| throws LdapException |
| { |
| Entry entry = modifyContext.getEntry(); |
| List<Modification> mods = modifyContext.getModItems(); |
| boolean hasModification = SCHEMA_UNCHANGED; |
| |
| // Check if the entry has a m-disabled attribute |
| Attribute disabledInEntry = entry.get( disabledAT ); |
| Modification disabledModification = ServerEntryUtils.getModificationItem( mods, disabledAT ); |
| |
| // The attribute might be present, but that does not mean we will change it. |
| // If it's absent, and if we have it in the previous entry, that mean we want |
| // to enable the schema |
| if ( disabledModification != null ) |
| { |
| // We are trying to modify the m-disabled attribute. |
| ModificationOperation modification = disabledModification.getOperation(); |
| Attribute attribute = disabledModification.getAttribute(); |
| |
| hasModification = modifyDisable( modifyContext, modification, attribute, disabledInEntry ); |
| } |
| else if ( disabledInEntry != null ) |
| { |
| hasModification = modifyDisable( modifyContext, ModificationOperation.REMOVE_ATTRIBUTE, null, |
| disabledInEntry ); |
| } |
| |
| return hasModification; |
| } |
| |
| |
| public void moveAndRename( Dn oriChildName, Dn newParentName, Rdn newRn, boolean deleteOldRn, Entry entry, |
| boolean cascaded ) throws LdapException |
| { |
| |
| } |
| |
| |
| /** |
| * Handles the addition of a metaSchema object to the schema partition. |
| * |
| * @param name the dn of the new metaSchema object |
| * @param entry the attributes of the new metaSchema object |
| */ |
| public void add( Entry entry ) throws LdapException |
| { |
| Dn dn = entry.getDn(); |
| Dn parentDn = dn.getParent(); |
| |
| if ( !parentDn.equals( ouSchemaDn ) ) |
| { |
| throw new LdapInvalidDnException( ResultCodeEnum.NAMING_VIOLATION, I18n.err( I18n.ERR_380, |
| ouSchemaDn.getName(), |
| parentDn.getNormName() ) ); |
| } |
| |
| // check if the new schema is enabled or disabled |
| boolean isEnabled = false; |
| Attribute disabled = entry.get( disabledAT ); |
| |
| if ( disabled == null ) |
| { |
| // If the attribute is absent, then the schema is enabled by default |
| isEnabled = true; |
| } |
| else if ( !disabled.contains( "TRUE" ) ) |
| { |
| isEnabled = true; |
| } |
| |
| // check to see that all dependencies are resolved and loaded if this |
| // schema is enabled, otherwise check that the dependency schemas exist |
| checkForDependencies( isEnabled, entry ); |
| |
| /* |
| * There's a slight problem that may result when adding a metaSchema |
| * object if the addition of the physical entry fails. If the schema |
| * is enabled when added in the condition tested below, that schema |
| * is added to the global registries. We need to add this so subsequent |
| * schema entity additions are loaded into the registries as they are |
| * added to the schema partition. However if the metaSchema object |
| * addition fails then we're left with this schema object looking like |
| * it is enabled in the registries object's schema hash. The effects |
| * of this are unpredictable. |
| * |
| * This whole problem is due to the inability of these handlers to |
| * react to a failed operation. To fix this we would need some way |
| * for these handlers to respond to failed operations and revert their |
| * effects on the registries. |
| * |
| * TODO: might want to add a set of failedOnXXX methods to the adapter |
| * where on failure the schema service calls the schema manager and it |
| * calls the appropriate methods on the respective handler. This way |
| * the schema manager can rollback registry changes when LDAP operations |
| * fail. |
| */ |
| |
| if ( isEnabled ) |
| { |
| Schema schema = factory.getSchema( entry ); |
| schemaManager.load( schema ); |
| } |
| } |
| |
| |
| /** |
| * Called to react to the deletion of a metaSchema object. This method |
| * simply removes the schema from the loaded schema map of the global |
| * registries. |
| * |
| * @param name the dn of the metaSchema object being deleted |
| * @param entry the attributes of the metaSchema object |
| */ |
| public void delete( Entry entry, boolean cascade ) throws LdapException |
| { |
| Attribute cn = entry.get( cnAT ); |
| String schemaName = cn.getString(); |
| |
| // Before allowing a schema object to be deleted we must check |
| // to make sure it's not depended upon by another schema |
| Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName ); |
| |
| if ( ( dependents != null ) && !dependents.isEmpty() ) |
| { |
| String msg = I18n.err( I18n.ERR_381, dependents ); |
| LOG.warn( msg ); |
| throw new LdapUnwillingToPerformException( |
| ResultCodeEnum.UNWILLING_TO_PERFORM, |
| msg ); |
| } |
| |
| // no need to check if schema is enabled or disabled here |
| // if not in the loaded set there will be no negative effect |
| schemaManager.unload( schemaName ); |
| } |
| |
| |
| /** |
| * Responds to the rdn (commonName) of the metaSchema object being |
| * changed. Changes all the schema entities associated with the |
| * renamed schema so they now map to a new schema name. |
| * |
| * @param name the dn of the metaSchema object renamed |
| * @param entry the entry of the metaSchema object before the rename |
| * @param newRdn the new commonName of the metaSchema object |
| */ |
| public void rename( Entry entry, Rdn newRdn, boolean cascade ) throws LdapException |
| { |
| String rdnAttribute = newRdn.getNormType(); |
| String rdnAttributeOid = schemaManager.getAttributeTypeRegistry().getOidByName( rdnAttribute ); |
| |
| if ( !rdnAttributeOid.equals( cnAT.getOid() ) ) |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| I18n.err( I18n.ERR_382, rdnAttribute ) ); |
| } |
| |
| /* |
| * This operation has to do the following: |
| * |
| * [1] check and make sure there are no dependent schemas on the |
| * one being renamed - if so an exception should result |
| * |
| * [2] make non-schema object registries modify the mapping |
| * for their entities: non-schema object registries contain |
| * objects that are not SchemaObjects and hence do not carry |
| * their schema within the object as a property |
| * |
| * [3] make schema object registries do the same but the way |
| * they do them will be different since these objects will |
| * need to be replaced or will require a setter for the |
| * schema name |
| */ |
| |
| // step [1] |
| /* |
| String schemaName = getSchemaName( entry.getDn() ); |
| Set<String> dependents = schemaManager.listDependentSchemaNames( schemaName ); |
| if ( ! dependents.isEmpty() ) |
| { |
| throw new LdapUnwillingToPerformException( |
| "Cannot allow a rename on " + schemaName + " schema while it has depentents.", |
| ResultCodeEnum.UNWILLING_TO_PERFORM ); |
| } |
| |
| // check if the new schema is enabled or disabled |
| boolean isEnabled = false; |
| EntryAttribute disabled = entry.get( disabledAT ); |
| |
| if ( disabled == null ) |
| { |
| isEnabled = true; |
| } |
| else if ( ! disabled.get().equals( "TRUE" ) ) |
| { |
| isEnabled = true; |
| } |
| |
| if ( ! isEnabled ) |
| { |
| return; |
| } |
| |
| // do steps 2 and 3 if the schema has been enabled and is loaded |
| |
| // step [2] |
| String newSchemaName = ( String ) newRdn.getUpValue(); |
| registries.getComparatorRegistry().renameSchema( schemaName, newSchemaName ); |
| registries.getNormalizerRegistry().renameSchema( schemaName, newSchemaName ); |
| registries.getSyntaxCheckerRegistry().renameSchema( schemaName, newSchemaName ); |
| |
| // step [3] |
| renameSchema( registries.getAttributeTypeRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getDitContentRuleRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getDitStructureRuleRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getMatchingRuleRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getMatchingRuleUseRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getNameFormRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getObjectClassRegistry(), schemaName, newSchemaName ); |
| renameSchema( registries.getLdapSyntaxRegistry(), schemaName, newSchemaName ); |
| */ |
| } |
| |
| |
| /** |
| * Moves are not allowed for metaSchema objects so this always throws an |
| * UNWILLING_TO_PERFORM LdapException. |
| */ |
| public void moveAndRename( Dn oriChildName, Dn newParentName, String newRn, boolean deleteOldRn, |
| Entry entry, boolean cascade ) throws LdapUnwillingToPerformException |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| I18n.err( I18n.ERR_383 ) ); |
| } |
| |
| |
| /** |
| * Moves are not allowed for metaSchema objects so this always throws an |
| * UNWILLING_TO_PERFORM LdapException. |
| */ |
| public void move( Dn oriChildName, Dn newParentName, |
| Entry entry, boolean cascade ) throws LdapUnwillingToPerformException |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| I18n.err( I18n.ERR_383 ) ); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // private utility methods |
| // ----------------------------------------------------------------------- |
| |
| /** |
| * Modify the Disable flag (the flag can be set to true or false). |
| * |
| * We can ADD, REMOVE or MODIFY this flag. The following matrix expose what will be the consequences |
| * of this operation, depending on the current state |
| * |
| * <pre> |
| * +-------------------+--------------------+--------------------+ |
| * op/state | TRUE | FALSE | ABSENT | |
| * +-------+-------+----------------------------------------+--------------------+ |
| * | ADD | TRUE | do nothing | do nothing | disable the schema | |
| * | +-------+-------------------+--------------------+--------------------+ |
| * | | FALSE | do nothing | do nothing | do nothing | |
| * +-------+-------+-------------------+--------------------+--------------------+ |
| * |REMOVE | N/A | enable the schema | do nothing | do nothing | |
| * +-------+-------+-------------------+--------------------+--------------------+ |
| * |MODIFY | TRUE | do nothing | disable the schema | disable the schema | |
| * | +-------+-------------------+--------------------+--------------------+ |
| * | | FALSE | enable the schema | do nothing | do nothing | |
| * +-------+-------+-------------------+--------------------+--------------------+ |
| * </pre> |
| */ |
| private boolean modifyDisable( ModifyOperationContext modifyContext, ModificationOperation modOp, |
| Attribute disabledInMods, Attribute disabledInEntry ) throws LdapException |
| { |
| Dn name = modifyContext.getDn(); |
| |
| switch ( modOp ) |
| { |
| /* |
| * If the user is adding a new m-disabled attribute to an enabled schema, |
| * we check that the value is "TRUE" and disable that schema if so. |
| */ |
| case ADD_ATTRIBUTE: |
| if ( disabledInEntry == null && "TRUE".equalsIgnoreCase( disabledInMods.getString() ) ) |
| { |
| return disableSchema( getSchemaName( name ) ); |
| } |
| |
| break; |
| |
| /* |
| * If the user is removing the m-disabled attribute we check if the schema is currently |
| * disabled. If so we enable the schema. |
| */ |
| case REMOVE_ATTRIBUTE: |
| if ( ( disabledInEntry != null ) && ( "TRUE".equalsIgnoreCase( disabledInEntry.getString() ) ) ) |
| { |
| return enableSchema( getSchemaName( name ) ); |
| } |
| |
| break; |
| |
| /* |
| * If the user is replacing the m-disabled attribute we check if the schema is |
| * currently disabled and enable it if the new state has it as enabled. If the |
| * schema is not disabled we disable it if the mods set m-disabled to true. |
| */ |
| case REPLACE_ATTRIBUTE: |
| |
| boolean isCurrentlyDisabled = false; |
| |
| if ( disabledInEntry != null ) |
| { |
| isCurrentlyDisabled = "TRUE".equalsIgnoreCase( disabledInEntry.getString() ); |
| } |
| |
| boolean isNewStateDisabled = false; |
| |
| if ( disabledInMods != null ) |
| { |
| Value<?> val = disabledInMods.get(); |
| |
| if ( val == null ) |
| { |
| isNewStateDisabled = false; |
| } |
| else |
| { |
| isNewStateDisabled = "TRUE".equalsIgnoreCase( val.getString() ); |
| } |
| } |
| |
| if ( isCurrentlyDisabled && !isNewStateDisabled ) |
| { |
| return enableSchema( getSchemaName( name ) ); |
| } |
| |
| if ( !isCurrentlyDisabled && isNewStateDisabled ) |
| { |
| return disableSchema( getSchemaName( name ) ); |
| } |
| |
| break; |
| |
| default: |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_384, modOp ) ); |
| } |
| |
| return SCHEMA_UNCHANGED; |
| } |
| |
| |
| private String getSchemaName( Dn schema ) |
| { |
| return schema.getRdn().getNormValue().getString(); |
| } |
| |
| |
| private boolean disableSchema( String schemaName ) throws LdapException |
| { |
| Schema schema = schemaManager.getLoadedSchema( schemaName ); |
| |
| if ( schema == null ) |
| { |
| // This is not possible. We can't enable a schema which is not loaded. |
| String msg = I18n.err( I18n.ERR_85, schemaName ); |
| LOG.error( msg ); |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, msg ); |
| } |
| |
| return schemaManager.disable( schemaName ); |
| |
| } |
| |
| |
| /** |
| * Enabling a schema consist on switching all of its schema element to enable. |
| * We have to do it on a temporary registries. |
| */ |
| private boolean enableSchema( String schemaName ) throws LdapException |
| { |
| Schema schema = schemaManager.getLoadedSchema( schemaName ); |
| |
| if ( schema == null ) |
| { |
| // We have to load the schema before enabling it. |
| schemaManager.loadDisabled( schemaName ); |
| } |
| |
| return schemaManager.enable( schemaName ); |
| } |
| |
| |
| /** |
| * Checks to make sure the dependencies either exist for disabled metaSchemas, |
| * or exist and are loaded (enabled) for enabled metaSchemas. |
| * |
| * @param isEnabled whether or not the new metaSchema is enabled |
| * @param entry the Attributes for the new metaSchema object |
| * @throws NamingException if the dependencies do not resolve or are not |
| * loaded (enabled) |
| */ |
| private void checkForDependencies( boolean isEnabled, Entry entry ) throws LdapException |
| { |
| Attribute dependencies = entry.get( this.dependenciesAT ); |
| |
| if ( dependencies == null ) |
| { |
| return; |
| } |
| |
| if ( isEnabled ) |
| { |
| // check to make sure all the dependencies are also enabled |
| Map<String, Schema> loaded = schemaManager.getRegistries().getLoadedSchemas(); |
| |
| for ( Value<?> value : dependencies ) |
| { |
| String dependency = value.getString(); |
| |
| if ( !loaded.containsKey( dependency ) ) |
| { |
| throw new LdapUnwillingToPerformException( |
| ResultCodeEnum.UNWILLING_TO_PERFORM, |
| "Unwilling to perform operation on enabled schema with disabled or missing dependencies: " |
| + dependency ); |
| } |
| } |
| } |
| else |
| { |
| for ( Value<?> value : dependencies ) |
| { |
| String dependency = value.getString(); |
| |
| if ( schemaManager.getLoadedSchema( Strings.toLowerCase( dependency ) ) == null ) |
| { |
| throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, |
| I18n.err( I18n.ERR_385, dependency ) ); |
| } |
| } |
| } |
| } |
| } |