blob: ae4cd3aeeb33ed32e68179a227b2c355f9029432 [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 java.util.ArrayList;
import java.util.List;
import javax.naming.NamingException;
import org.apache.directory.server.constants.ApacheSchemaConstants;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.entry.ClonedServerEntry;
import org.apache.directory.server.core.entry.DefaultServerAttribute;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerModification;
import org.apache.directory.server.core.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.BindOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
import org.apache.directory.server.core.interceptor.context.ListOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
import org.apache.directory.server.core.interceptor.context.OperationContext;
import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.interceptor.context.UnbindOperationContext;
import org.apache.directory.server.core.partition.AbstractPartition;
import org.apache.directory.server.core.partition.ByPassConstants;
import org.apache.directory.server.core.partition.NullPartition;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.schema.registries.synchronizers.RegistrySynchronizerAdaptor;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.message.control.CascadeControl;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.SchemaManager;
import org.apache.directory.shared.ldap.schema.SchemaUtils;
import org.apache.directory.shared.ldap.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A special partition designed to contain the portion of the DIT where schema
* information for the server is stored.
*
* In an effort to make sure that all Partition implementations are equal
* citizens to ApacheDS we want to be able to swap in and out any kind of
* Partition to store schema. This also has the added advantage of making
* sure the core, and hence the server is not dependent on any specific
* partition, which reduces coupling in the server's modules.
*
* The SchemaPartition achieves this by not really being a backing store
* itself for the schema entries. It instead delegates to another Partition
* via containment. It delegates all calls to this contained Partition. While
* doing so it also manages certain things:
*
* <ol>
* <li>Checks that schema changes are valid.</li>
* <li>Updates the schema Registries on valid schema changes making sure
* the schema on disk is in sync with the schema in memory.
* </li>
* <li>Will eventually manage transaction based changes to schema where
* between some sequence of operations the schema may be inconsistent.
* </li>
* <li>Delegates read/write operations to contained Partition.</li>
* <li>
* Responsible for initializing schema for the entire server. ApacheDS
* cannot start up other partitions until this Partition is started
* without having access to the Registries. This Partition supplies the
* Registries on initialization for the server. That's one of it's core
* responsibilities.
* </li>
*
* So by containing another Partition, we abstract the storage mechanism away
* from the management responsibilities while decoupling the server from a
* specific partition implementation and removing complexity in the Schema
* interceptor service which before managed synchronization.
* </ol>
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
public final class SchemaPartition extends AbstractPartition
{
/** the logger */
private static final Logger LOG = LoggerFactory.getLogger( SchemaPartition.class );
/** the fixed id: 'schema' */
private static final String ID = "schema";
/** the wrapped Partition */
private Partition wrapped = new NullPartition();
/** schema manager */
private SchemaManager schemaManager;
/** registry synchronizer adaptor */
private RegistrySynchronizerAdaptor synchronizer;
/** A static DN for the ou=schemaModifications entry */
private static LdapDN schemaModificationDN;
/**
* Sets the wrapped {@link Partition} which must be supplied or
* {@link Partition#initialize()} will fail with a NullPointerException.
*
* @param wrapped the Partition being wrapped
*/
public void setWrappedPartition( Partition wrapped )
{
if ( this.isInitialized() )
{
throw new IllegalStateException( "Not allowed to set the wrappedPartition after initialization." );
}
this.wrapped = wrapped;
}
/**
* Gets the {@link Partition} being wrapped.
*
* @return the wrapped Partition
*/
public Partition getWrappedPartition()
{
return wrapped;
}
/**
* Get's the ID which is fixed: 'schema'.
*/
public final String getId()
{
return ID;
}
/**
* Has no affect: the id is fixed at {@link SchemaPartition#ID}: 'schema'.
* A warning is logged.
*/
public final void setId( String id )
{
LOG.warn( "This partition's ID is fixed: {}", ID );
}
/**
* Always returns {@link ServerDNConstants#OU_SCHEMA_DN_NORMALIZED}: '2.5.4.11=schema'.
*/
public final LdapDN getSuffixDn()
{
return wrapped.getSuffixDn();
}
/**
* Always returns {@link ServerDNConstants#OU_SCHEMA_DN}: 'ou=schema'.
*/
public final String getSuffix()
{
return SchemaConstants.OU_SCHEMA;
}
/**
* Has no affect: just logs a warning.
*/
public final void setSuffix( String suffix )
{
LOG.warn( "This partition's suffix is fixed: {}", SchemaConstants.OU_SCHEMA );
}
// -----------------------------------------------------------------------
// Partition Interface Method Overrides
// -----------------------------------------------------------------------
@Override
public void sync() throws Exception
{
wrapped.sync();
}
@Override
protected void doInit() throws Exception
{
// -----------------------------------------------------------------------
// Load apachemeta schema from within the ldap-schema Jar with all the
// schema it depends on. This is a minimal mandatory set of schemas.
// -----------------------------------------------------------------------
//SerializableComparator.setSchemaManager( schemaManager );
wrapped.setId( ID );
wrapped.setSuffix( SchemaConstants.OU_SCHEMA );
wrapped.getSuffixDn().normalize( schemaManager.getNormalizerMapping() );
wrapped.setSchemaManager( schemaManager );
try
{
wrapped.initialize();
PartitionSchemaLoader partitionLoader = new PartitionSchemaLoader( wrapped, schemaManager );
synchronizer = new RegistrySynchronizerAdaptor( schemaManager );
if ( wrapped instanceof NullPartition )
{
LOG.warn( "BYPASSING CRITICAL SCHEMA PROCESSING CODE DURING HEAVY DEV. " +
"PLEASE REMOVE THIS CONDITION BY USING A VALID SCHEMA PARTITION!!!" );
return;
}
}
catch ( Exception e )
{
LOG.error( "Failed to initialize wrapped partition.", e );
throw new RuntimeException( e );
}
schemaModificationDN = new LdapDN( ServerDNConstants.SCHEMA_MODIFICATIONS_DN );
schemaModificationDN.normalize( schemaManager.getNormalizerMapping() );
}
@Override
protected void doDestroy()
{
try
{
wrapped.destroy();
}
catch ( Exception e )
{
LOG.error( "Attempt to destroy wrapped partition failed.", e );
throw new RuntimeException( e );
}
}
// -----------------------------------------------------------------------
// Partition Interface Methods
// -----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public void add( AddOperationContext opContext ) throws Exception
{
// At this point, the added SchemaObject does not exist in the partition
// We have to check if it's enabled and then inject it into the registries
// but only if it does not break the server.
synchronizer.add( opContext );
// Now, write the newly added SchemaObject into the schemaPartition
try
{
wrapped.add( opContext );
}
catch ( Exception e )
{
// If something went wrong, we have to unregister the schemaObject
// from the registries
// TODO : deregister the newly added element.
throw e;
}
updateSchemaModificationAttributes( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#bind(org.apache.directory.server.core.interceptor.context.BindOperationContext)
*/
public void bind( BindOperationContext opContext ) throws Exception
{
wrapped.bind( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#delete(org.apache.directory.server.core.interceptor.context.DeleteOperationContext)
*/
public void delete( DeleteOperationContext opContext ) throws Exception
{
boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID );
// The SchemaObject always exist when we reach this method.
synchronizer.delete( opContext, cascade );
try
{
wrapped.delete( opContext );
}
catch ( Exception e )
{
// TODO : If something went wrong, what should we do here ?
throw e;
}
updateSchemaModificationAttributes( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#list(org.apache.directory.server.core.interceptor.context.ListOperationContext)
*/
public EntryFilteringCursor list( ListOperationContext opContext ) throws Exception
{
return wrapped.list( opContext );
}
/**
* {@inheritDoc}
*/
public boolean hasEntry( EntryOperationContext entryContext ) throws Exception
{
return wrapped.hasEntry( entryContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#lookup(java.lang.Long)
*/
public ClonedServerEntry lookup( Long id ) throws Exception
{
return wrapped.lookup( id );
}
/**
* {@inheritDoc}
*/
public void modify( ModifyOperationContext opContext ) throws Exception
{
ServerEntry entry = opContext.getEntry();
if ( entry == null )
{
LookupOperationContext lookupCtx = new LookupOperationContext( opContext.getSession(), opContext.getDn() );
entry = wrapped.lookup( lookupCtx );
}
ServerEntry targetEntry = ( ServerEntry ) SchemaUtils.getTargetEntry(
opContext.getModItems(), entry );
boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID );
boolean hasModification = synchronizer.modify( opContext, targetEntry, cascade );
if ( hasModification )
{
wrapped.modify( opContext );
}
if ( !opContext.getDn().equals( schemaModificationDN ) )
{
updateSchemaModificationAttributes( opContext );
}
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#move(org.apache.directory.server.core.interceptor.context.MoveOperationContext)
*/
public void move( MoveOperationContext opContext ) throws Exception
{
boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID );
ClonedServerEntry entry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
synchronizer.move( opContext, entry, cascade );
wrapped.move( opContext );
updateSchemaModificationAttributes( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#moveAndRename(org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext)
*/
public void moveAndRename( MoveAndRenameOperationContext opContext ) throws Exception
{
boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID );
ClonedServerEntry entry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
synchronizer.moveAndRename( opContext, entry, cascade );
wrapped.moveAndRename( opContext );
updateSchemaModificationAttributes( opContext );
}
/**
* {@inheritDoc}
*/
public void rename( RenameOperationContext opContext ) throws Exception
{
boolean cascade = opContext.hasRequestControl( CascadeControl.CONTROL_OID );
// First update the registries
synchronizer.rename( opContext, cascade );
// Update the schema partition
wrapped.rename( opContext );
// Update the SSSE operational attributes
updateSchemaModificationAttributes( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#search(org.apache.directory.server.core.interceptor.context.SearchOperationContext)
*/
public EntryFilteringCursor search( SearchOperationContext opContext ) throws Exception
{
return wrapped.search( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#unbind(org.apache.directory.server.core.interceptor.context.UnbindOperationContext)
*/
public void unbind( UnbindOperationContext opContext ) throws Exception
{
wrapped.unbind( opContext );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.partition.Partition#lookup(org.apache.directory.server.core.interceptor.context.LookupOperationContext)
*/
public ClonedServerEntry lookup( LookupOperationContext lookupContext ) throws Exception
{
return wrapped.lookup( lookupContext );
}
/**
* Updates the schemaModifiersName and schemaModifyTimestamp attributes of
* the schemaModificationAttributes entry for the global schema at
* ou=schema,cn=schemaModifications. This entry is hardcoded at that
* position for now.
*
* The current time is used to set the timestamp and the DN of current user
* is set for the modifiersName.
*
* @throws NamingException if the update fails
*/
private void updateSchemaModificationAttributes( OperationContext opContext ) throws Exception
{
String modifiersName = opContext.getSession().getEffectivePrincipal().getJndiName().getNormName();
String modifyTimestamp = DateUtils.getGeneralizedTime();
List<Modification> mods = new ArrayList<Modification>( 2 );
mods.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE,
new DefaultServerAttribute(
ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT,
schemaManager.lookupAttributeTypeRegistry( ApacheSchemaConstants.SCHEMA_MODIFY_TIMESTAMP_AT ),
modifyTimestamp ) ) );
mods.add( new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE,
new DefaultServerAttribute(
ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT,
schemaManager.lookupAttributeTypeRegistry( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT ),
modifiersName ) ) );
opContext.modify( schemaModificationDN, mods, ByPassConstants.SCHEMA_MODIFICATION_ATTRIBUTES_UPDATE_BYPASS );
}
/**
* @param schemaManager the SchemaManager to set
*/
public void setSchemaManager( SchemaManager schemaManager )
{
this.schemaManager = schemaManager;
}
/**
* @return The schemaManager
*/
public SchemaManager getSchemaManager()
{
return schemaManager;
}
/**
* @see Object#toString()
*/
public String toString()
{
return "Partition : " + ID;
}
}