blob: 9f4c37d13658b9b168d68b6a616d688ff1b728a8 [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.ldap.handlers;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.AbandonListener;
import org.apache.directory.api.ldap.model.message.AbandonableRequest;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchResultEntry;
import org.apache.directory.api.ldap.model.message.SearchResultEntryImpl;
import org.apache.directory.api.ldap.model.message.controls.ChangeType;
import org.apache.directory.api.ldap.model.message.controls.EntryChange;
import org.apache.directory.api.ldap.model.message.controls.EntryChangeImpl;
import org.apache.directory.api.ldap.model.message.controls.PersistentSearch;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
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.event.DirectoryListener;
import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.api.interceptor.context.ChangeOperationContext;
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.RenameOperationContext;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.server.ldap.LdapSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A DirectoryListener implementation which sends back added, deleted, modified or
* renamed entries to a client that created this listener. This class is part of the
* persistent search implementation which uses the event notification scheme built into
* the server core.
*
* This listener is disabled only when a session closes or when an abandon request
* cancels it. Hence time and size limits in normal search operations do not apply
* here.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class PersistentSearchListener implements DirectoryListener, AbandonListener
{
private static final Logger LOG = LoggerFactory.getLogger( PersistentSearchListener.class );
final LdapSession session;
final SearchRequest req;
final PersistentSearch psearchControl;
private LookupOperationContext filterCtx;
private SchemaManager schemaManager;
public PersistentSearchListener( LdapSession session, SearchRequest req )
{
this.session = session;
this.req = req;
req.addAbandonListener( this );
this.psearchControl = ( PersistentSearch ) req.getControls().get( PersistentSearch.OID );
filterCtx = new LookupOperationContext( session.getCoreSession(), req.getAttributes().toArray( new String[]{} ) );
schemaManager = session.getCoreSession().getDirectoryService().getSchemaManager();
}
@Override
public boolean isSynchronous()
{
return false; // always asynchronous
}
public void abandon() throws LdapException
{
// must abandon the operation
session.getCoreSession().getDirectoryService().getEventService().removeListener( this );
/*
* From RFC 2251 Section 4.11:
*
* In the event that a server receives an Abandon Request on a Search
* operation in the midst of transmitting responses to the Search, that
* server MUST cease transmitting entry responses to the abandoned
* request immediately, and MUST NOT send the SearchResultDone. Of
* course, the server MUST ensure that only properly encoded LDAPMessage
* PDUs are transmitted.
*
* SO DON'T SEND BACK ANYTHING!!!!!
*/
}
public void requestAbandoned( AbandonableRequest req )
{
try
{
abandon();
}
catch ( LdapException e )
{
LOG.error( I18n.err( I18n.ERR_164 ), e );
}
}
private void setECResponseControl( SearchResultEntry response, ChangeOperationContext opContext, ChangeType type )
{
if ( psearchControl.isReturnECs() )
{
EntryChange ecControl = new EntryChangeImpl();
ecControl.setChangeType( type );
if ( opContext.getChangeLogEvent() != null )
{
ecControl.setChangeNumber( opContext.getChangeLogEvent().getRevision() );
}
if ( opContext instanceof RenameOperationContext || opContext instanceof MoveOperationContext )
{
ecControl.setPreviousDn( opContext.getDn() );
}
response.addControl( ecControl );
}
}
public void entryAdded( AddOperationContext addContext )
{
if ( !psearchControl.isNotificationEnabled( ChangeType.ADD ) )
{
return;
}
SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() );
respEntry.setObjectName( addContext.getDn() );
// the entry needs to be cloned cause addContext.getEntry() will only contain
// the user provided values and all the operational attributes added during
// Partition.add() will be applied in the cloned entry present inside it
// if we don't clone then the attributes will not be filtered
// e.x the operational attributes will also be sent even when a user requests
// user attributes only
Entry entry = new ClonedServerEntry( addContext.getEntry() );
filterEntry( entry );
respEntry.setEntry( entry );
setECResponseControl( respEntry, addContext, ChangeType.ADD );
session.getIoSession().write( respEntry );
}
public void entryDeleted( DeleteOperationContext deleteContext )
{
if ( !psearchControl.isNotificationEnabled( ChangeType.DELETE ) )
{
return;
}
SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() );
respEntry.setObjectName( deleteContext.getDn() );
filterEntry( deleteContext.getEntry() );
respEntry.setEntry( deleteContext.getEntry() );
setECResponseControl( respEntry, deleteContext, ChangeType.DELETE );
session.getIoSession().write( respEntry );
}
public void entryModified( ModifyOperationContext modifyContext )
{
if ( !psearchControl.isNotificationEnabled( ChangeType.MODIFY ) )
{
return;
}
SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() );
respEntry.setObjectName( modifyContext.getDn() );
Entry entry = new ClonedServerEntry( modifyContext.getAlteredEntry() );
filterEntry( entry );
respEntry.setEntry( entry );
setECResponseControl( respEntry, modifyContext, ChangeType.MODIFY );
session.getIoSession().write( respEntry );
}
public void entryMoved( MoveOperationContext moveContext )
{
if ( !psearchControl.isNotificationEnabled( ChangeType.MODDN ) )
{
return;
}
SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() );
respEntry.setObjectName( moveContext.getNewDn() );
Entry entry = new ClonedServerEntry( moveContext.getModifiedEntry() );
filterEntry( entry );
respEntry.setEntry( entry );
setECResponseControl( respEntry, moveContext, ChangeType.MODDN );
session.getIoSession().write( respEntry );
}
public void entryMovedAndRenamed( MoveAndRenameOperationContext moveAndRenameContext )
{
entryRenamed( moveAndRenameContext );
}
public void entryRenamed( RenameOperationContext renameContext )
{
if ( !psearchControl.isNotificationEnabled( ChangeType.MODDN ) )
{
return;
}
SearchResultEntry respEntry = new SearchResultEntryImpl( req.getMessageId() );
respEntry.setObjectName( renameContext.getModifiedEntry().getDn() );
Entry entry = new ClonedServerEntry( renameContext.getModifiedEntry() );
filterEntry( entry );
respEntry.setEntry( entry );
setECResponseControl( respEntry, renameContext, ChangeType.MODDN );
session.getIoSession().write( respEntry );
}
/**
* A convenient method to filter the contents of an entry
*
* @see ServerEntryUtils#filterContents(SchemaManager, org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext, Entry)
*
* @param entry
*/
private void filterEntry( Entry entry )
{
try
{
ServerEntryUtils.filterContents( schemaManager, filterCtx, entry );
}
catch( LdapException e )
{
// shouldn't happen, if it does then blow up
throw new RuntimeException( e );
}
}
}