blob: 9ca3e535161938bbbd3885348c9e6893d9b4c77c [file] [log] [blame]
/*
*
* Licensed 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.support;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingExceptionEvent;
import javax.naming.event.ObjectChangeListener;
import org.apache.directory.server.core.jndi.ServerLdapContext;
import org.apache.directory.server.ldap.SessionRegistry;
import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
import org.apache.directory.shared.ldap.exception.LdapException;
import org.apache.directory.shared.ldap.exception.OperationAbandonedException;
import org.apache.directory.shared.ldap.message.AbandonListener;
import org.apache.directory.shared.ldap.message.AbandonableRequest;
import org.apache.directory.shared.ldap.message.EntryChangeControl;
import org.apache.directory.shared.ldap.message.LdapResult;
import org.apache.directory.shared.ldap.message.PersistentSearchControl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.SearchRequest;
import org.apache.directory.shared.ldap.message.SearchResponseEntry;
import org.apache.directory.shared.ldap.message.SearchResponseEntryImpl;
import org.apache.directory.shared.ldap.util.ExceptionUtils;
import org.apache.mina.common.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A JNDI NamingListener 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 is exposed by the server side ApacheDS JNDI LDAP provider.
*
* 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>
* @version $Rev$
*/
class PersistentSearchListener implements ObjectChangeListener, NamespaceChangeListener, AbandonListener
{
private static final Logger log = LoggerFactory.getLogger( SearchHandler.class );
final ServerLdapContext ctx;
final IoSession session;
final SearchRequest req;
final PersistentSearchControl control;
PersistentSearchListener(ServerLdapContext ctx, IoSession session, SearchRequest req)
{
this.session = session;
this.req = req;
req.addAbandonListener( this );
this.ctx = ctx;
this.control = ( PersistentSearchControl ) req.getControls().get( PersistentSearchControl.CONTROL_OID );
}
public void abandon() throws NamingException
{
// must abandon the operation
ctx.removeNamingListener( 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 namingExceptionThrown( NamingExceptionEvent evt )
{
// must abandon the operation and send response done with an
// error message if this occurs because something is wrong
try
{
ctx.removeNamingListener( this );
}
catch ( NamingException e )
{
log.error( "Attempt to remove listener from context failed", e );
}
/*
* 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!!!!!
*/
if ( evt.getException() instanceof OperationAbandonedException )
{
return;
}
SessionRegistry.getSingleton().removeOutstandingRequest( session, new Integer( req.getMessageId() ) );
String msg = "failed on persistent search operation";
if ( log.isDebugEnabled() )
{
msg += ":\n" + req + ":\n" + ExceptionUtils.getStackTrace( evt.getException() );
}
ResultCodeEnum code = null;
if ( evt.getException() instanceof LdapException )
{
code = ( ( LdapException ) evt.getException() ).getResultCode();
}
else
{
code = ResultCodeEnum.getBestEstimate( evt.getException(), req.getType() );
}
LdapResult result = req.getResultResponse().getLdapResult();
result.setResultCode( code );
result.setErrorMessage( msg );
if ( ( evt.getException().getResolvedName() != null )
&& ( ( code == ResultCodeEnum.NOSUCHOBJECT ) || ( code == ResultCodeEnum.ALIASPROBLEM )
|| ( code == ResultCodeEnum.INVALIDDNSYNTAX ) || ( code == ResultCodeEnum.ALIASDEREFERENCINGPROBLEM ) ) )
{
result.setMatchedDn( evt.getException().getResolvedName().toString() );
}
session.write( req.getResultResponse() );
}
public void objectChanged( NamingEvent evt )
{
// send the entry back
sendEntry( evt );
}
public void objectAdded( NamingEvent evt )
{
// send the entry back
sendEntry( evt );
}
public void objectRemoved( NamingEvent evt )
{
// send the entry back
sendEntry( evt );
}
public void objectRenamed( NamingEvent evt )
{
// send the entry back
sendEntry( evt );
}
private void sendEntry( NamingEvent evt )
{
/*
* @todo eventually you'll want to add the changeNumber once we move
* the CSN functionality into the server.
*/
SearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
EntryChangeControl ecControl = null;
if ( control.isReturnECs() )
{
ecControl = new EntryChangeControl();
respEntry.add( ecControl );
}
switch ( evt.getType() )
{
case ( NamingEvent.OBJECT_ADDED ):
if ( !control.isNotificationEnabled( ChangeType.ADD ) )
return;
respEntry.setObjectName( evt.getNewBinding().getName() );
respEntry.setAttributes( ( Attributes ) evt.getChangeInfo() );
if ( ecControl != null )
{
ecControl.setChangeType( ChangeType.ADD );
}
break;
case ( NamingEvent.OBJECT_CHANGED ):
if ( !control.isNotificationEnabled( ChangeType.MODIFY ) )
return;
respEntry.setObjectName( evt.getOldBinding().getName() );
respEntry.setAttributes( ( Attributes ) evt.getOldBinding().getObject() );
if ( ecControl != null )
{
ecControl.setChangeType( ChangeType.MODIFY );
}
break;
case ( NamingEvent.OBJECT_REMOVED ):
if ( !control.isNotificationEnabled( ChangeType.DELETE ) )
return;
respEntry.setObjectName( evt.getOldBinding().getName() );
respEntry.setAttributes( ( Attributes ) evt.getOldBinding().getObject() );
if ( ecControl != null )
{
ecControl.setChangeType( ChangeType.DELETE );
}
break;
case ( NamingEvent.OBJECT_RENAMED ):
if ( !control.isNotificationEnabled( ChangeType.MODDN ) )
return;
respEntry.setObjectName( evt.getNewBinding().getName() );
respEntry.setAttributes( ( Attributes ) evt.getNewBinding().getObject() );
if ( ecControl != null )
{
ecControl.setChangeType( ChangeType.MODDN );
ecControl.setPreviousDn( evt.getOldBinding().getName() );
}
break;
default:
return;
}
session.write( respEntry );
}
public void requestAbandoned( AbandonableRequest req )
{
try
{
abandon();
}
catch ( NamingException e )
{
log.error( "failed to properly abandon this persistent search", e );
}
}
}