blob: e1328f6c14e5493761efb5bedebca3f4a340c133 [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.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;
}
}