blob: cec0fcf803834fbd0fb70c0b42debd192be84887 [file] [log] [blame]
/*
* 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.schema;
import org.apache.directory.server.constants.MetaSchemaConstants;
import org.apache.directory.server.core.entry.ServerAttribute;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerEntryUtils;
import org.apache.directory.server.schema.bootstrap.Schema;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.server.schema.registries.SchemaObjectRegistry;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.entry.EntryAttribute;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.Value;
import org.apache.directory.shared.ldap.exception.LdapInvalidNameException;
import org.apache.directory.shared.ldap.exception.LdapOperationNotSupportedException;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.name.Rdn;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.schema.SchemaObject;
import javax.naming.NamingException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Handles events where entries of objectClass metaSchema are modified.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public class MetaSchemaHandler implements SchemaChangeHandler
{
private final SchemaEntityFactory factory;
private final PartitionSchemaLoader loader;
private final Registries globalRegistries;
private final AttributeType disabledAT;
private final String OU_OID;
private final AttributeType cnAT;
private final AttributeType dependenciesAT;
public MetaSchemaHandler( Registries globalRegistries, PartitionSchemaLoader loader ) throws NamingException
{
this.globalRegistries = globalRegistries;
this.disabledAT = globalRegistries.getAttributeTypeRegistry().lookup( MetaSchemaConstants.M_DISABLED_AT );
this.loader = loader;
this.OU_OID = globalRegistries.getOidRegistry().getOid( SchemaConstants.OU_AT );
this.factory = new SchemaEntityFactory( globalRegistries );
this.cnAT = globalRegistries.getAttributeTypeRegistry().lookup( SchemaConstants.CN_AT );
this.dependenciesAT = globalRegistries.getAttributeTypeRegistry()
.lookup( MetaSchemaConstants.M_DEPENDENCIES_AT );
}
/**
* Reacts to modification of a metaSchema object. At this point the
* only considerable changes are to the m-disabled and the
* m-dependencies attributes.
*
* @param name the dn of the metaSchema object modified
* @param modOp the type of modification operation being performed
* @param mods the attribute modifications as an Attributes object
* @param entry the entry after the modifications have been applied
*/
public void modify( LdapDN name, ModificationOperation modOp, ServerEntry mods, ServerEntry entry,
ServerEntry targetEntry, boolean cascade ) throws NamingException
{
EntryAttribute disabledInMods = mods.get( disabledAT );
if ( disabledInMods != null )
{
disable( name, modOp, disabledInMods, entry.get( disabledAT ) );
}
// check if the new schema is enabled or disabled
boolean isEnabled = false;
EntryAttribute disabled = targetEntry.get( this.disabledAT );
if ( disabled == null )
{
isEnabled = true;
}
else if ( ! disabled.getString().equals( "TRUE" ) )
{
isEnabled = true;
}
EntryAttribute dependencies = mods.get( dependenciesAT );
if ( dependencies != null )
{
checkForDependencies( isEnabled, targetEntry );
}
}
/**
* Reacts to modification of a metaSchema object. At this point the
* only considerable changes are to the m-disabled and the
* m-dependencies attributes.
*
* @param name the dn of the metaSchema object modified
* @param mods the attribute modifications as an ModificationItem arry
* @param entry the entry after the modifications have been applied
*/
public void modify( LdapDN name, List<Modification> mods, ServerEntry entry,
ServerEntry targetEntry, boolean cascade ) throws NamingException
{
EntryAttribute disabledInEntry = entry.get( disabledAT );
Modification disabledModification = ServerEntryUtils.getModificationItem( mods, disabledAT );
if ( disabledModification != null )
{
disable( name,
disabledModification.getOperation(),
(ServerAttribute)disabledModification.getAttribute(),
disabledInEntry );
}
// check if the new schema is enabled or disabled
boolean isEnabled = false;
EntryAttribute disabled = targetEntry.get( disabledAT );
if ( disabled == null )
{
isEnabled = true;
}
else if ( ! disabled.contains( "TRUE" ) )
{
isEnabled = true;
}
ServerAttribute dependencies = ServerEntryUtils.getAttribute( mods, dependenciesAT );
if ( dependencies != null )
{
checkForDependencies( isEnabled, targetEntry );
}
}
public void move( LdapDN oriChildName, LdapDN newParentName, Rdn newRn, boolean deleteOldRn, ServerEntry entry, boolean cascaded ) throws NamingException
{
}
/**
* 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( LdapDN name, ServerEntry entry ) throws NamingException
{
LdapDN parentDn = ( LdapDN ) name.clone();
parentDn.remove( parentDn.size() - 1 );
parentDn.normalize( globalRegistries.getAttributeTypeRegistry().getNormalizerMapping() );
if ( !parentDn.toNormName().equals( OU_OID + "=schema" ) )
{
throw new LdapInvalidNameException( "The parent dn of a schema should be " + OU_OID + "=schema and not: "
+ parentDn.toNormName(), ResultCodeEnum.NAMING_VIOLATION );
}
// 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.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 unpredicatable.
*
* 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 );
globalRegistries.addToLoadedSet( 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( LdapDN name, ServerEntry entry, boolean cascade ) throws NamingException
{
EntryAttribute 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 = loader.listDependentSchemaNames( schemaName );
if ( ! dependents.isEmpty() )
{
throw new LdapOperationNotSupportedException(
"Cannot delete schema that has dependents: " + dependents,
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
// no need to check if schema is enabled or disabled here
// if not in the loaded set there will be no negative effect
globalRegistries.removeFromLoadedSet( 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( LdapDN name, ServerEntry entry, Rdn newRdn, boolean cascade ) throws NamingException
{
String rdnAttribute = newRdn.getUpType();
String rdnAttributeOid = globalRegistries.getOidRegistry().getOid( rdnAttribute );
if ( ! rdnAttributeOid.equals( cnAT.getOid() ) )
{
throw new LdapOperationNotSupportedException(
"Cannot allow rename with rdnAttribute set to "
+ rdnAttribute + ": cn must be used instead." ,
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
/*
* 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( name );
Set<String> dependents = loader.listDependentSchemaNames( schemaName );
if ( ! dependents.isEmpty() )
{
throw new LdapOperationNotSupportedException(
"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();
globalRegistries.getComparatorRegistry().renameSchema( schemaName, newSchemaName );
globalRegistries.getNormalizerRegistry().renameSchema( schemaName, newSchemaName );
globalRegistries.getSyntaxCheckerRegistry().renameSchema( schemaName, newSchemaName );
// step [3]
renameSchema( globalRegistries.getAttributeTypeRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getDitContentRuleRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getDitStructureRuleRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getMatchingRuleRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getMatchingRuleUseRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getNameFormRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getObjectClassRegistry(), schemaName, newSchemaName );
renameSchema( globalRegistries.getSyntaxRegistry(), schemaName, newSchemaName );
}
/**
* Moves are not allowed for metaSchema objects so this always throws an
* UNWILLING_TO_PERFORM LdapException.
*/
public void move( LdapDN oriChildName, LdapDN newParentName, String newRn, boolean deleteOldRn,
ServerEntry entry, boolean cascade ) throws NamingException
{
throw new LdapOperationNotSupportedException( "Moving around schemas is not allowed.",
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
/**
* Moves are not allowed for metaSchema objects so this always throws an
* UNWILLING_TO_PERFORM LdapException.
*/
public void replace( LdapDN oriChildName, LdapDN newParentName,
ServerEntry entry, boolean cascade ) throws NamingException
{
throw new LdapOperationNotSupportedException( "Moving around schemas is not allowed.",
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
// -----------------------------------------------------------------------
// private utility methods
// -----------------------------------------------------------------------
private void disable( LdapDN name, ModificationOperation modOp, EntryAttribute disabledInMods, EntryAttribute disabledInEntry )
throws NamingException
{
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 )
{
if ( "TRUE".equalsIgnoreCase( disabledInMods.getString() ) )
{
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 ( "TRUE".equalsIgnoreCase( disabledInEntry.getString() ) )
{
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 = "TRUE".equalsIgnoreCase( disabledInEntry.getString() );
boolean isNewStateDisabled = "TRUE".equalsIgnoreCase( disabledInMods.getString() );
if ( isCurrentlyDisabled && !isNewStateDisabled )
{
enableSchema( getSchemaName( name ) );
break;
}
if ( !isCurrentlyDisabled && isNewStateDisabled )
{
disableSchema( getSchemaName( name ) );
}
break;
default:
throw new IllegalArgumentException( "Unknown modify operation type: " + modOp );
}
}
private String getSchemaName( LdapDN schema )
{
return ( String ) schema.getRdn().getValue();
}
private void disableSchema( String schemaName ) throws NamingException
{
Set<String> dependents = loader.listEnabledDependentSchemaNames( schemaName );
if ( ! dependents.isEmpty() )
{
throw new LdapOperationNotSupportedException(
"Cannot disable schema with enabled dependents: " + dependents,
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
globalRegistries.unload( schemaName );
}
/**
* TODO - for now we're just going to add the schema to the global
* registries ... we may need to add it to more than that though later.
*/
private void enableSchema( String schemaName ) throws NamingException
{
if ( globalRegistries.getLoadedSchemas().containsKey( schemaName ) )
{
// TODO log warning: schemaName + " was already loaded"
return;
}
Schema schema = loader.getSchema( schemaName );
loader.loadWithDependencies( schema, globalRegistries );
}
/**
* 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, ServerEntry entry ) throws NamingException
{
EntryAttribute 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 = globalRegistries.getLoadedSchemas();
for ( Value<?> value:dependencies )
{
String dependency = ( String ) value.get();
if ( ! loaded.containsKey( dependency ) )
{
throw new LdapOperationNotSupportedException(
"Unwilling to perform operation on enabled schema with disabled or missing dependencies: "
+ dependency, ResultCodeEnum.UNWILLING_TO_PERFORM );
}
}
}
else
{
Set<String> allSchemas = loader.getSchemaNames();
for ( Value<?> value:dependencies )
{
String dependency = ( String ) value.get();
if ( ! allSchemas.contains( dependency ) )
{
throw new LdapOperationNotSupportedException(
"Unwilling to perform operation on schema with missing dependencies: " + dependency,
ResultCodeEnum.UNWILLING_TO_PERFORM );
}
}
}
}
/**
* Used to iterate through SchemaObjects in a SchemaObjectRegistry and rename
* their schema property to a new schema name.
*
* @param registry the registry whose objects are changed
* @param originalSchemaName the original schema name
* @param newSchemaName the new schema name
*/
private void renameSchema( SchemaObjectRegistry registry, String originalSchemaName, String newSchemaName )
{
Iterator<? extends SchemaObject> list = registry.iterator();
while ( list.hasNext() )
{
SchemaObject obj = list.next();
if ( obj.getSchema().equalsIgnoreCase( originalSchemaName ) )
{
obj.setSchema( newSchemaName );
}
}
}
}