| /* |
| * 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.changelog; |
| |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| 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.DefaultEntry; |
| 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.exception.LdapException; |
| import org.apache.directory.api.ldap.model.ldif.ChangeType; |
| import org.apache.directory.api.ldap.model.ldif.LdifEntry; |
| import org.apache.directory.api.ldap.model.ldif.LdifRevertor; |
| import org.apache.directory.api.ldap.model.message.controls.ManageDsaITImpl; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.server.constants.ApacheSchemaConstants; |
| import org.apache.directory.server.constants.ServerDNConstants; |
| import org.apache.directory.server.core.api.CoreSession; |
| import org.apache.directory.server.core.api.DirectoryService; |
| import org.apache.directory.server.core.api.InterceptorEnum; |
| import org.apache.directory.server.core.api.changelog.ChangeLog; |
| import org.apache.directory.server.core.api.entry.ClonedServerEntry; |
| import org.apache.directory.server.core.api.entry.ServerEntryUtils; |
| import org.apache.directory.server.core.api.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.OperationContext; |
| import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; |
| import org.apache.directory.server.core.shared.SchemaService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An interceptor which intercepts write operations to the directory and |
| * logs them with the server's ChangeLog service. |
| * Note: Adding/deleting a tag is not recorded as a change |
| */ |
| public class ChangeLogInterceptor extends BaseInterceptor |
| { |
| /** for debugging */ |
| private static final Logger LOG = LoggerFactory.getLogger( ChangeLogInterceptor.class ); |
| |
| /** used to ignore modify operations to tombstone entries */ |
| private AttributeType entryDeleted; |
| |
| /** the changelog service to log changes to */ |
| private ChangeLog changeLog; |
| |
| /** OID of the 'rev' attribute used in changeLogEvent and tag objectclasses */ |
| private static final String REV_AT_OID = "1.3.6.1.4.1.18060.0.4.1.2.47"; |
| |
| |
| /** |
| * Creates a new instance of a ChangeLogInterceptor. |
| */ |
| public ChangeLogInterceptor() |
| { |
| super( InterceptorEnum.CHANGE_LOG_INTERCEPTOR ); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Overridden init() and destroy() methods |
| // ----------------------------------------------------------------------- |
| /** |
| * The init method will initialize the local variables and load the |
| * entryDeleted AttributeType. |
| */ |
| public void init( DirectoryService directoryService ) throws LdapException |
| { |
| super.init( directoryService ); |
| |
| changeLog = directoryService.getChangeLog(); |
| entryDeleted = directoryService.getSchemaManager() |
| .getAttributeType( ApacheSchemaConstants.ENTRY_DELETED_AT_OID ); |
| } |
| |
| |
| // ----------------------------------------------------------------------- |
| // Overridden (only change inducing) intercepted methods |
| // ----------------------------------------------------------------------- |
| /** |
| * {@inheritDoc} |
| */ |
| public void add( AddOperationContext addContext ) throws LdapException |
| { |
| next( addContext ); |
| |
| if ( !changeLog.isEnabled() ) |
| { |
| return; |
| } |
| |
| Entry addEntry = addContext.getEntry(); |
| |
| // we don't want to record addition of a tag as a change |
| if ( addEntry.get( REV_AT_OID ) != null ) |
| { |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.Add ); |
| forward.setDn( addContext.getDn() ); |
| |
| for ( Attribute attribute : addEntry.getAttributes() ) |
| { |
| AttributeType attributeType = attribute.getAttributeType(); |
| forward.addAttribute( addEntry.get( attributeType ).clone() ); |
| } |
| |
| LdifEntry reverse = LdifRevertor.reverseAdd( addContext.getDn() ); |
| addContext.setChangeLogEvent( changeLog.log( getPrincipal( addContext ), forward, reverse ) ); |
| } |
| |
| |
| /** |
| * The delete operation has to be stored with a way to restore the deleted element. |
| * There is no way to do that but reading the entry and dump it into the LOG. |
| */ |
| /** |
| * {@inheritDoc} |
| */ |
| public void delete( DeleteOperationContext deleteContext ) throws LdapException |
| { |
| // @todo make sure we're not putting in operational attributes that cannot be user modified |
| // must save the entry if change log is enabled |
| Entry serverEntry = null; |
| |
| if ( changeLog.isEnabled() ) |
| { |
| serverEntry = getAttributes( deleteContext ); |
| } |
| |
| next( deleteContext ); |
| |
| if ( !changeLog.isEnabled() ) |
| { |
| return; |
| } |
| |
| // we don't want to record deleting a tag as a change |
| if ( serverEntry.get( REV_AT_OID ) != null ) |
| { |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.Delete ); |
| forward.setDn( deleteContext.getDn() ); |
| |
| Entry reverseEntry = new DefaultEntry( serverEntry.getDn() ); |
| |
| boolean isCollectiveSubentry = serverEntry.hasObjectClass( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ); |
| |
| for ( Attribute attribute : serverEntry ) |
| { |
| // filter collective attributes, they can't be added by the revert operation |
| AttributeType at = schemaManager.lookupAttributeTypeRegistry( attribute.getId() ); |
| |
| if ( !at.isCollective() || isCollectiveSubentry ) |
| { |
| reverseEntry.add( attribute.clone() ); |
| } |
| } |
| |
| LdifEntry reverse = LdifRevertor.reverseDel( deleteContext.getDn(), reverseEntry ); |
| deleteContext.setChangeLogEvent( changeLog.log( getPrincipal( deleteContext ), forward, reverse ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void modify( ModifyOperationContext modifyContext ) throws LdapException |
| { |
| Entry serverEntry = null; |
| Modification modification = ServerEntryUtils.getModificationItem( modifyContext.getModItems(), entryDeleted ); |
| boolean isDelete = ( modification != null ); |
| |
| if ( !isDelete && ( changeLog.isEnabled() ) ) |
| { |
| // @todo make sure we're not putting in operational attributes that cannot be user modified |
| serverEntry = getAttributes( modifyContext ); |
| } |
| |
| // Duplicate modifications so that the reverse does not contain the operational attributes |
| List<Modification> clonedMods = new ArrayList<Modification>(); |
| |
| for ( Modification mod : modifyContext.getModItems() ) |
| { |
| clonedMods.add( mod.clone() ); |
| } |
| |
| // Call the next interceptor |
| next( modifyContext ); |
| |
| // @TODO: needs big consideration!!! |
| // NOTE: perhaps we need to log this as a system operation that cannot and should not be reapplied? |
| if ( isDelete || |
| !changeLog.isEnabled() || |
| |
| // if there are no modifications due to stripping out bogus non- |
| // existing attributes then we will have no modification items and |
| // should ignore not this without registering it with the changelog |
| |
| modifyContext.getModItems().size() == 0 ) |
| { |
| if ( isDelete ) |
| { |
| LOG.debug( "Bypassing changelog on modify of entryDeleted attribute." ); |
| } |
| |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.Modify ); |
| forward.setDn( modifyContext.getDn() ); |
| |
| List<Modification> mods = new ArrayList<Modification>( clonedMods.size() ); |
| |
| for ( Modification modItem : clonedMods ) |
| { |
| // TODO: handle correctly http://issues.apache.org/jira/browse/DIRSERVER-1198 |
| mods.add( modItem ); |
| |
| forward.addModification( modItem ); |
| } |
| |
| Entry clientEntry = new DefaultEntry( serverEntry.getDn() ); |
| |
| for ( Attribute attribute : serverEntry ) |
| { |
| clientEntry.add( attribute.clone() ); |
| } |
| |
| LdifEntry reverse = LdifRevertor.reverseModify( |
| modifyContext.getDn(), |
| mods, |
| clientEntry ); |
| |
| modifyContext.setChangeLogEvent( changeLog.log( getPrincipal( modifyContext ), forward, reverse ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void move( MoveOperationContext moveContext ) throws LdapException |
| { |
| next( moveContext ); |
| |
| if ( !changeLog.isEnabled() ) |
| { |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.ModDn ); |
| forward.setDn( moveContext.getDn() ); |
| forward.setNewSuperior( moveContext.getNewSuperior().getName() ); |
| |
| LdifEntry reverse = LdifRevertor.reverseMove( moveContext.getNewSuperior(), moveContext.getDn() ); |
| moveContext.setChangeLogEvent( changeLog.log( getPrincipal( moveContext ), forward, reverse ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException |
| { |
| Entry serverEntry = null; |
| |
| if ( changeLog.isEnabled() ) |
| { |
| // @todo make sure we're not putting in operational attributes that cannot be user modified |
| serverEntry = moveAndRenameContext.getOriginalEntry(); |
| } |
| |
| next( moveAndRenameContext ); |
| |
| if ( !changeLog.isEnabled() ) |
| { |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.ModDn ); |
| forward.setDn( moveAndRenameContext.getDn() ); |
| forward.setDeleteOldRdn( moveAndRenameContext.getDeleteOldRdn() ); |
| forward.setNewRdn( moveAndRenameContext.getNewRdn().getName() ); |
| forward.setNewSuperior( moveAndRenameContext.getNewSuperiorDn().getName() ); |
| |
| List<LdifEntry> reverses = LdifRevertor.reverseMoveAndRename( |
| serverEntry, moveAndRenameContext.getNewSuperiorDn(), moveAndRenameContext.getNewRdn(), false ); |
| |
| if ( moveAndRenameContext.isReferralIgnored() ) |
| { |
| forward.addControl( new ManageDsaITImpl() ); |
| LdifEntry reversedEntry = reverses.get( 0 ); |
| reversedEntry.addControl( new ManageDsaITImpl() ); |
| } |
| |
| moveAndRenameContext |
| .setChangeLogEvent( changeLog.log( getPrincipal( moveAndRenameContext ), forward, reverses ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void rename( RenameOperationContext renameContext ) throws LdapException |
| { |
| Entry serverEntry = null; |
| |
| if ( renameContext.getEntry() != null ) |
| { |
| serverEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry(); |
| } |
| |
| next( renameContext ); |
| |
| // After this point, the entry has been modified. The cloned entry contains |
| // the modified entry, the originalEntry has changed |
| |
| if ( !changeLog.isEnabled() ) |
| { |
| return; |
| } |
| |
| LdifEntry forward = new LdifEntry(); |
| forward.setChangeType( ChangeType.ModRdn ); |
| forward.setDn( renameContext.getDn() ); |
| forward.setNewRdn( renameContext.getNewRdn().getName() ); |
| forward.setDeleteOldRdn( renameContext.getDeleteOldRdn() ); |
| |
| List<LdifEntry> reverses = LdifRevertor.reverseRename( |
| serverEntry, renameContext.getNewRdn(), renameContext.getDeleteOldRdn() ); |
| |
| renameContext.setChangeLogEvent( changeLog.log( getPrincipal( renameContext ), forward, reverses ) ); |
| } |
| |
| |
| /** |
| * Gets attributes required for modifications. |
| * |
| * @param dn the dn of the entry to get |
| * @return the entry's attributes (may be immutable if the schema subentry) |
| * @throws Exception on error accessing the entry's attributes |
| */ |
| private Entry getAttributes( OperationContext opContext ) throws LdapException |
| { |
| Dn dn = opContext.getDn(); |
| Entry serverEntry; |
| |
| // @todo make sure we're not putting in operational attributes that cannot be user modified |
| if ( dn.equals( ServerDNConstants.CN_SCHEMA_DN ) ) |
| { |
| return SchemaService.getSubschemaEntryCloned( directoryService ); |
| } |
| else |
| { |
| CoreSession session = opContext.getSession(); |
| LookupOperationContext lookupContext = new LookupOperationContext( session, dn, SchemaConstants.ALL_ATTRIBUTES_ARRAY ); |
| serverEntry = directoryService.getPartitionNexus().lookup( lookupContext ); |
| } |
| |
| return serverEntry; |
| } |
| } |