| /* |
| * 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; |
| } |
| } |