blob: 208679799cb86c59bd750432a3d6ecebe156bc1c [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 org.apache.directory.server.constants.ApacheSchemaConstants;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.interceptor.BaseInterceptor;
import org.apache.directory.server.core.interceptor.NextInterceptor;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
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.RenameOperationContext;
import org.apache.directory.server.core.invocation.Invocation;
import org.apache.directory.server.core.invocation.InvocationStack;
import org.apache.directory.server.core.partition.PartitionNexusProxy;
import org.apache.directory.server.core.schema.SchemaService;
import org.apache.directory.shared.ldap.ldif.ChangeType;
import org.apache.directory.shared.ldap.ldif.Entry;
import org.apache.directory.shared.ldap.ldif.LdifUtils;
import org.apache.directory.shared.ldap.message.ModificationItemImpl;
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.util.AttributeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
/**
* An interceptor which intercepts write operations to the directory and
* logs them with the server's ChangeLog service.
*/
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;
/** we need the schema service to deal with special conditions */
private SchemaService schemaService;
// -----------------------------------------------------------------------
// Overridden init() and destroy() methods
// -----------------------------------------------------------------------
public void init( DirectoryService directoryService ) throws NamingException
{
super.init( directoryService );
changeLog = directoryService.getChangeLog();
schemaService = directoryService.getSchemaService();
entryDeleted = directoryService.getRegistries().getAttributeTypeRegistry()
.lookup( ApacheSchemaConstants.ENTRY_DELETED_OID );
}
// -----------------------------------------------------------------------
// Overridden (only change inducing) intercepted methods
// -----------------------------------------------------------------------
public void add( NextInterceptor next, AddOperationContext opContext ) throws NamingException
{
next.add( opContext );
if ( ! changeLog.isEnabled() || opContext.isCollateralOperation() )
{
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.Add );
forward.setDn( opContext.getDn().getUpName() );
NamingEnumeration<? extends Attribute> list = opContext.getEntry().getAll();
while ( list.hasMore() )
{
forward.addAttribute( ( Attribute ) list.next() );
}
Entry reverse = LdifUtils.reverseAdd( opContext.getDn() );
changeLog.log( getPrincipal(), 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.
*/
public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws NamingException
{
// @todo make sure we're not putting in operational attributes that cannot be user modified
// must save the entry if change log is enabled
Attributes attributes = null;
if ( changeLog.isEnabled() && ! opContext.isCollateralOperation() )
{
attributes = getAttributes( opContext.getDn() );
}
next.delete( opContext );
if ( ! changeLog.isEnabled() || opContext.isCollateralOperation() )
{
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.Delete );
forward.setDn( opContext.getDn().getUpName() );
Entry reverse = LdifUtils.reverseDel( opContext.getDn(), attributes );
changeLog.log( getPrincipal(), forward, reverse );
}
/**
* 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 NamingException on error accessing the entry's attributes
*/
private Attributes getAttributes( LdapDN dn ) throws NamingException
{
Attributes attributes;
// @todo make sure we're not putting in operational attributes that cannot be user modified
Invocation invocation = InvocationStack.getInstance().peek();
PartitionNexusProxy proxy = invocation.getProxy();
if ( schemaService.isSchemaSubentry( dn.toNormName() ) )
{
return schemaService.getSubschemaEntryCloned();
}
else
{
attributes = proxy.lookup( new LookupOperationContext( dn ), PartitionNexusProxy.LOOKUP_BYPASS );
}
return attributes;
}
public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws NamingException
{
Attributes attributes = null;
boolean isDelete = AttributeUtils.getAttribute( opContext.getModItems(), entryDeleted ) != null;
if ( ! isDelete && ( changeLog.isEnabled() && ! opContext.isCollateralOperation() ) )
{
// @todo make sure we're not putting in operational attributes that cannot be user modified
attributes = getAttributes( opContext.getDn() );
}
next.modify( opContext );
// @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() || opContext.isCollateralOperation() )
{
if ( isDelete )
{
LOG.debug( "Bypassing changelog on modify of entryDeleted attribute." );
}
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.Modify );
forward.setDn( opContext.getDn().getUpName() );
for ( ModificationItemImpl modItem : opContext.getModItems() )
{
forward.addModificationItem( modItem );
}
Entry reverse = LdifUtils.reverseModify( opContext.getDn(), opContext.getModItems(), attributes );
changeLog.log( getPrincipal(), forward, reverse );
}
// -----------------------------------------------------------------------
// Though part left as an exercise (Not Any More!)
// -----------------------------------------------------------------------
public void rename ( NextInterceptor next, RenameOperationContext renameContext ) throws NamingException
{
Attributes attributes = null;
if ( changeLog.isEnabled() && ! renameContext.isCollateralOperation() )
{
// @todo make sure we're not putting in operational attributes that cannot be user modified
attributes = getAttributes( renameContext.getDn() );
}
next.rename( renameContext );
if ( ! changeLog.isEnabled() || renameContext.isCollateralOperation() )
{
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.ModRdn );
forward.setDn( renameContext.getDn().getUpName() );
forward.setDeleteOldRdn( renameContext.getDelOldDn() );
Entry reverse = LdifUtils.reverseModifyRdn( attributes, null, renameContext.getDn(),
new Rdn( renameContext.getNewRdn() ) );
changeLog.log( getPrincipal(), forward, reverse );
}
public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opCtx )
throws NamingException
{
Attributes attributes = null;
if ( changeLog.isEnabled() && ! opCtx.isCollateralOperation() )
{
// @todo make sure we're not putting in operational attributes that cannot be user modified
Invocation invocation = InvocationStack.getInstance().peek();
PartitionNexusProxy proxy = invocation.getProxy();
attributes = proxy.lookup( new LookupOperationContext( opCtx.getDn() ),
PartitionNexusProxy.LOOKUP_BYPASS );
}
next.moveAndRename( opCtx );
if ( ! changeLog.isEnabled() || opCtx.isCollateralOperation() )
{
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.ModDn );
forward.setDn( opCtx.getDn().getUpName() );
forward.setDeleteOldRdn( opCtx.getDelOldDn() );
forward.setNewRdn( opCtx.getNewRdn().getUpName() );
forward.setNewSuperior( opCtx.getParent().getUpName() );
Entry reverse = LdifUtils.reverseModifyRdn( attributes, opCtx.getParent(), opCtx.getDn(),
new Rdn( opCtx.getNewRdn() ) );
changeLog.log( getPrincipal(), forward, reverse );
}
public void move ( NextInterceptor next, MoveOperationContext opCtx ) throws NamingException
{
next.move( opCtx );
if ( ! changeLog.isEnabled() || opCtx.isCollateralOperation() )
{
return;
}
Entry forward = new Entry();
forward.setChangeType( ChangeType.ModDn );
forward.setDn( opCtx.getDn().getUpName() );
forward.setNewSuperior( opCtx.getParent().getUpName() );
Entry reverse = LdifUtils.reverseModifyDn( opCtx.getParent(), opCtx.getDn() );
changeLog.log( getPrincipal(), forward, reverse );
}
}