You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by ak...@apache.org on 2006/01/06 07:43:49 UTC
svn commit: r366439 - in /directory/trunk:
apacheds-server-unit/src/test/java/org/apache/ldap/server/
ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/
Author: akarasulu
Date: Thu Jan 5 22:43:39 2006
New Revision: 366439
URL: http://svn.apache.org/viewcvs?rev=366439&view=rev
Log:
cleaned up the search handler and added support for psearch changesOnly field
Added:
directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/ChangeListener.java
directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/PersistentSearchListener.java
directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchResponseIterator.java
Modified:
directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java
directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java
Added: directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/ChangeListener.java
URL: http://svn.apache.org/viewcvs/directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/ChangeListener.java?rev=366439&view=auto
==============================================================================
--- directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/ChangeListener.java (added)
+++ directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/ChangeListener.java Thu Jan 5 22:43:39 2006
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2004 The Apache Software Foundation
+ *
+ * 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.ldap.server;
+
+
+import java.util.Hashtable;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.HasControls;
+import javax.naming.ldap.InitialLdapContext;
+
+import org.apache.ldap.common.codec.search.controls.EntryChangeControl;
+import org.apache.ldap.common.codec.search.controls.EntryChangeControlDecoder;
+import org.apache.ldap.common.message.PersistentSearchControl;
+
+
+/**
+ * A simple change listener application that prints out changes returned using
+ * the psearch control.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ChangeListener
+{
+ public static void main( String[] args ) throws Exception
+ {
+ Hashtable env = new Hashtable();
+ env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ env.put( "java.naming.provider.url", "ldap://localhost:10389/ou=system" );
+ env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ env.put( "java.naming.security.credentials", "secret" );
+ env.put( "java.naming.security.authentication", "simple" );
+ InitialLdapContext ctx = new InitialLdapContext( env, null );
+ PersistentSearchControl control = new PersistentSearchControl();
+ control.setChangesOnly( false );
+ control.setReturnECs( true );
+ control.setCritical( true );
+ control.setChangeTypes( PersistentSearchControl.ALL_CHANGES );
+ Control[] ctxCtls = new Control[] { control };
+
+ try
+ {
+ Control[] respCtls;
+ ctx.setRequestControls( ctxCtls );
+ EntryChangeControl ecCtl = null;
+ NamingEnumeration list = ctx.search( "", "objectClass=*", null );
+ while( list.hasMore() )
+ {
+ SearchResult result = ( SearchResult ) list.next();
+ if ( result instanceof HasControls )
+ {
+ respCtls = ( ( HasControls ) result ).getControls();
+ if ( respCtls != null )
+ {
+ for ( int ii = 0; ii < respCtls.length; ii ++ )
+ {
+ if ( respCtls[ii].getID().equals(
+ org.apache.ldap.common.message.EntryChangeControl.CONTROL_ID ) )
+ {
+ EntryChangeControlDecoder decoder = new EntryChangeControlDecoder();
+ ecCtl = ( EntryChangeControl ) decoder.decode( respCtls[ii].getEncodedValue() );
+ }
+ }
+ }
+ }
+
+ StringBuffer buf = new StringBuffer();
+ buf.append( "DN: " ).append( result.getName() ).append( "\n" );
+ if ( ecCtl != null )
+ {
+ System.out.println( "================ NOTIFICATION ================" );
+ buf.append( " EntryChangeControl =\n" );
+ buf.append( " changeType : " ).append( ecCtl.getChangeType() ).append( "\n" );
+ buf.append( " previousDN : " ).append( ecCtl.getPreviousDn() ).append( "\n" );
+ buf.append( " changeNumber : " ).append( ecCtl.getChangeNumber() ).append( "\n" );
+ }
+
+ System.out.println( buf.toString() );
+
+ if ( ecCtl != null )
+ {
+ System.out.println( "==============================================" );
+ }
+ }
+ }
+ catch( Exception e )
+ {
+ e.printStackTrace();
+ }
+ }
+}
+
Modified: directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java
URL: http://svn.apache.org/viewcvs/directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java?rev=366439&r1=366438&r2=366439&view=diff
==============================================================================
--- directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java (original)
+++ directory/trunk/apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java Thu Jan 5 22:43:39 2006
@@ -475,10 +475,54 @@
}
+ /**
+ * Shows correct notifications for add(1) changes with returned
+ * EntryChangeControl and changesOnly set to false so we return
+ * the first set of entries.
+ *
+ * This test is commented out because it exhibits some producer
+ * consumer lockups (server and client being in same process)
+ *
+ * PLUS ALL THIS GARBAGE IS TIME DEPENDENT!!!!!
+ */
+// public void testPsearchAddWithECAndFalseChangesOnly() throws Exception
+// {
+// PersistentSearchControl control = new PersistentSearchControl();
+// control.setReturnECs( true );
+// control.setChangesOnly( false );
+// PSearchListener listener = new PSearchListener( control );
+// Thread t = new Thread( listener );
+// t.start();
+//
+// Thread.sleep( 3000 );
+//
+// assertEquals( 5, listener.count );
+// ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+//
+// long start = System.currentTimeMillis();
+// while ( t.isAlive() )
+// {
+// Thread.sleep( 100 );
+// if ( System.currentTimeMillis() - start > 3000 )
+// {
+// System.out.println( "PSearchListener thread not dead yet" );
+// break;
+// }
+// }
+//
+// assertEquals( 6, listener.count );
+// assertNotNull( listener.result );
+// // darn it getting normalized name back
+// assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+// assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
+// }
+
+
class PSearchListener implements Runnable
{
boolean isReady = false;
PSearchNotification result;
+ int count = 0;
final PersistentSearchControl control;
PSearchListener() { control = new PersistentSearchControl(); }
@@ -500,6 +544,7 @@
{
Control[] controls = null;
SearchResult sresult = ( SearchResult ) list.next();
+ count++;
if ( sresult instanceof HasControls )
{
controls = ( ( HasControls ) sresult ).getControls();
Added: directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/PersistentSearchListener.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/PersistentSearchListener.java?rev=366439&view=auto
==============================================================================
--- directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/PersistentSearchListener.java (added)
+++ directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/PersistentSearchListener.java Thu Jan 5 22:43:39 2006
@@ -0,0 +1,254 @@
+/*
+ *
+ * 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.ldap.server.protocol.support;
+
+
+import java.util.Iterator;
+
+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.ldap.common.codec.search.controls.ChangeType;
+import org.apache.ldap.common.exception.LdapException;
+import org.apache.ldap.common.message.Control;
+import org.apache.ldap.common.message.EntryChangeControl;
+import org.apache.ldap.common.message.LdapResultImpl;
+import org.apache.ldap.common.message.PersistentSearchControl;
+import org.apache.ldap.common.message.ResultCodeEnum;
+import org.apache.ldap.common.message.SearchRequest;
+import org.apache.ldap.common.message.SearchResponseDone;
+import org.apache.ldap.common.message.SearchResponseDoneImpl;
+import org.apache.ldap.common.message.SearchResponseEntry;
+import org.apache.ldap.common.message.SearchResponseEntryImpl;
+import org.apache.ldap.common.util.ExceptionUtils;
+import org.apache.ldap.server.jndi.ServerLdapContext;
+import org.apache.ldap.server.protocol.SessionRegistry;
+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
+{
+ 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;
+ this.ctx = ctx;
+ this.req.put( "PersistentSearchHandler", this );
+ this.control = getPersistentSearchControl( req );
+ }
+
+
+ public void abandon() throws NamingException
+ {
+ // must abandon the operation and send response done with success
+ ctx.removeNamingListener( this );
+
+ // remove from outstanding map
+ SessionRegistry.getSingleton().removeOutstandingRequest( session, new Integer( req.getMessageId() ) );
+
+ // send successful response back to client
+ SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
+ resp.setLdapResult( new LdapResultImpl( resp ) );
+ resp.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
+ resp.getLdapResult().setMatchedDn( req.getBase() );
+ session.write( resp );
+ }
+
+
+ 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 );
+ }
+
+ 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() );
+ }
+
+ SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
+ ResultCodeEnum code = null;
+
+ if( evt.getException() instanceof LdapException )
+ {
+ code = ( ( LdapException ) evt.getException() ).getResultCode();
+ }
+ else
+ {
+ code = ResultCodeEnum.getBestEstimate( evt.getException(), req.getType() );
+ }
+
+ resp.setLdapResult( new LdapResultImpl( resp ) );
+ resp.getLdapResult().setResultCode( code );
+ resp.getLdapResult().setErrorMessage( msg );
+
+ if ( ( evt.getException().getResolvedName() != null ) &&
+ ( ( code == ResultCodeEnum.NOSUCHOBJECT ) ||
+ ( code == ResultCodeEnum.ALIASPROBLEM ) ||
+ ( code == ResultCodeEnum.INVALIDDNSYNTAX ) ||
+ ( code == ResultCodeEnum.ALIASDEREFERENCINGPROBLEM ) ) )
+ {
+ resp.getLdapResult().setMatchedDn( evt.getException().getResolvedName().toString() );
+ }
+
+ session.write( resp );
+ }
+
+
+ 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 );
+ }
+
+
+ /**
+ * Searches for and returns the PersistentSearchControl in the request if present.
+ *
+ * @param req the request searched
+ * @return the psearch control or null if one does not exist for this req
+ */
+ static PersistentSearchControl getPersistentSearchControl( SearchRequest req )
+ {
+ Iterator list = req.getControls().iterator();
+ while ( list.hasNext() )
+ {
+ Control control = ( Control ) list.next();
+ if ( control.getID().equals( "2.16.840.1.113730.3.4.3" ) )
+ {
+ return ( PersistentSearchControl ) control;
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
Modified: directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java?rev=366439&r1=366438&r2=366439&view=diff
==============================================================================
--- directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java (original)
+++ directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java Thu Jan 5 22:43:39 2006
@@ -20,37 +20,23 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.NoSuchElementException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.event.NamespaceChangeListener;
-import javax.naming.event.NamingEvent;
-import javax.naming.event.NamingExceptionEvent;
-import javax.naming.event.ObjectChangeListener;
import javax.naming.ldap.LdapContext;
-import org.apache.ldap.common.codec.search.controls.ChangeType;
+import org.apache.ldap.common.codec.util.LdapResultEnum;
import org.apache.ldap.common.exception.LdapException;
import org.apache.ldap.common.filter.PresenceNode;
-import org.apache.ldap.common.message.Control;
-import org.apache.ldap.common.message.EntryChangeControl;
import org.apache.ldap.common.message.LdapResultImpl;
import org.apache.ldap.common.message.PersistentSearchControl;
-import org.apache.ldap.common.message.ReferralImpl;
+import org.apache.ldap.common.message.Response;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.message.ScopeEnum;
import org.apache.ldap.common.message.SearchRequest;
import org.apache.ldap.common.message.SearchResponseDone;
import org.apache.ldap.common.message.SearchResponseDoneImpl;
-import org.apache.ldap.common.message.SearchResponseEntry;
-import org.apache.ldap.common.message.SearchResponseEntryImpl;
-import org.apache.ldap.common.message.SearchResponseReference;
-import org.apache.ldap.common.message.SearchResponseReferenceImpl;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.util.ArrayUtils;
import org.apache.ldap.common.util.ExceptionUtils;
@@ -75,13 +61,55 @@
private static final Logger log = LoggerFactory.getLogger( SearchHandler.class );
private static final String DEREFALIASES_KEY = "java.naming.ldap.derefAliases";
+
+ /**
+ * Builds the JNDI search controls for a SearchRequest.
+ *
+ * @param req the search request.
+ * @param ids the ids to return
+ * @return the SearchControls to use with the ApacheDS server side JNDI provider
+ */
+ private static SearchControls getSearchControls( SearchRequest req, String[] ids )
+ {
+ // prepare all the search controls
+ SearchControls controls = new SearchControls();
+ controls.setCountLimit( req.getSizeLimit() );
+ controls.setTimeLimit( req.getTimeLimit() );
+ controls.setSearchScope( req.getScope().getValue() );
+ controls.setReturningObjFlag( req.getTypesOnly() );
+ controls.setReturningAttributes( ids );
+ controls.setDerefLinkFlag( true );
+ return controls;
+ }
+
+
+ /**
+ * Determines if a search request is on the RootDSE of the server.
+ *
+ * @param req the request issued
+ * @return true if the search is on the RootDSE false otherwise
+ */
+ private static boolean isRootDSESearch( SearchRequest req )
+ {
+ boolean isBaseIsRoot = req.getBase().trim().equals( "" );
+ boolean isBaseScope = req.getScope() == ScopeEnum.BASEOBJECT;
+ boolean isRootDSEFilter = false;
+ if ( req.getFilter() instanceof PresenceNode )
+ {
+ isRootDSEFilter = ( ( PresenceNode ) req.getFilter() ).getAttribute().equalsIgnoreCase( "objectClass" );
+ }
+ return isBaseIsRoot && isBaseScope && isRootDSEFilter;
+ }
+
+ /**
+ * Main message handing method for search requests.
+ */
public void messageReceived( IoSession session, Object request )
{
ServerLdapContext ctx;
SearchRequest req = ( SearchRequest ) request;
NamingEnumeration list = null;
-
String[] ids = null;
Collection retAttrs = new HashSet();
retAttrs.addAll( req.getAttributes() );
@@ -96,27 +124,15 @@
{
ids = ( String[] ) retAttrs.toArray( ArrayUtils.EMPTY_STRING_ARRAY );
}
-
- // prepare all the search controls
- SearchControls controls = new SearchControls();
- controls.setCountLimit( req.getSizeLimit() );
- controls.setTimeLimit( req.getTimeLimit() );
- controls.setSearchScope( req.getScope().getValue() );
- controls.setReturningObjFlag( req.getTypesOnly() );
- controls.setReturningAttributes( ids );
- controls.setDerefLinkFlag( true );
+ SearchControls controls = getSearchControls( req, ids );
try
{
- boolean isBaseIsRoot = req.getBase().trim().equals( "" );
- boolean isBaseScope = req.getScope() == ScopeEnum.BASEOBJECT;
- boolean isRootDSEFilter = false;
- if ( req.getFilter() instanceof PresenceNode )
- {
- isRootDSEFilter = ( ( PresenceNode ) req.getFilter() ).getAttribute().equalsIgnoreCase( "objectClass" );
- }
- boolean isRootDSESearch = isBaseIsRoot && isBaseScope && isRootDSEFilter;
+ // ===============================================================
+ // Find session context
+ // ===============================================================
+ boolean isRootDSESearch = isRootDSESearch( req );
// bypass checks to disallow anonymous binds for search on RootDSE with base obj scope
if ( isRootDSESearch )
{
@@ -143,6 +159,11 @@
ctx = ( ServerLdapContext ) unknown;
}
}
+ ctx.addToEnvironment( DEREFALIASES_KEY, req.getDerefAliases().getName() );
+
+ // ===============================================================
+ // Handle annonymous binds
+ // ===============================================================
StartupConfiguration cfg = ( StartupConfiguration ) Configuration.toConfiguration( ctx.getEnvironment() );
boolean allowAnonymousBinds = cfg.isAllowAnonymousAccess();
@@ -160,29 +181,65 @@
return;
}
- /*
- * Persistent Search Implementation
- * --------------------------------
- *
- * The persistent search implementation uses the event notification scheme build into
- * the core and exposes access to it via the JNDI EventContext. A psearch will not
- * return until an abandon request cancels it. Hence time and size limits as weill normal
- * search operations do not apply. In this handler we simply setup the structures to
- * trickle back PDUs as we recieve event notifications using this IoSession.
- *
- * We need structures in Ldap p-p to track outstanding operations to implement AbandonOperations.
- */
- PersistentSearchControl psearchControl = getPersistentSearchControl( req );
+ // ===============================================================
+ // Handle psearch differently
+ // ===============================================================
+
+ PersistentSearchControl psearchControl = PersistentSearchListener.getPersistentSearchControl( req );
if ( psearchControl != null )
{
- PersistentSearchHandler handler = new PersistentSearchHandler( ctx, session, req );
+ // there are no limits for psearch processing
+ controls.setCountLimit( 0 );
+ controls.setTimeLimit( 0 );
+
+ if ( ! psearchControl.isChangesOnly() )
+ {
+ list = ( ( ServerLdapContext ) ctx ).search( new LdapName( req.getBase() ), req.getFilter(), controls );
+ if( list.hasMore() )
+ {
+ Iterator it = new SearchResponseIterator( req, list );
+ while( it.hasNext() )
+ {
+ Response resp = ( Response ) it.next();
+ if ( resp instanceof SearchResponseDone )
+ {
+ // ok if normal search beforehand failed somehow quickly abandon psearch
+ ResultCodeEnum rcode = ( ( SearchResponseDone ) resp ).getLdapResult().getResultCode();
+ if ( rcode.getValue() != LdapResultEnum.SUCCESS )
+ {
+ session.write( resp );
+ return;
+ }
+ // if search was fine then we returned all entries so now
+ // instead of returning the DONE response we break from the
+ // loop and user the notification listener to send back
+ // notificationss to the client in never ending search
+ else break;
+ }
+ else
+ {
+ session.write( resp );
+ }
+ }
+ }
+ }
+
+ // now we process entries for ever as they change
+ PersistentSearchListener handler = new PersistentSearchListener( ctx, session, req );
StringBuffer buf = new StringBuffer();
req.getFilter().printToBuffer( buf );
ctx.addNamingListener( req.getBase(), buf.toString(), controls, handler );
return;
}
- ctx.addToEnvironment( DEREFALIASES_KEY, req.getDerefAliases().getName() );
+ // ===============================================================
+ // Handle regular search requests from here down
+ // ===============================================================
+
+ /*
+ * Iterate through all search results building and sending back responses
+ * for each search result returned.
+ */
list = ( ( ServerLdapContext ) ctx ).search( new LdapName( req.getBase() ), req.getFilter(), controls );
if( list.hasMore() )
{
@@ -250,451 +307,6 @@
{
session.write( it.next() );
}
- }
- }
-
-
- SearchResponseDone getResponse( SearchRequest req, NamingException e )
- {
- String msg = "failed on search operation";
-
- if ( log.isDebugEnabled() )
- {
- msg += ":\n" + req + ":\n" + ExceptionUtils.getStackTrace( e );
- }
-
- SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
- ResultCodeEnum code = null;
-
- if( e instanceof LdapException )
- {
- code = ( ( LdapException ) e ).getResultCode();
- }
- else
- {
- code = ResultCodeEnum.getBestEstimate( e, req.getType() );
- }
-
- resp.setLdapResult( new LdapResultImpl( resp ) );
- resp.getLdapResult().setResultCode( code );
- resp.getLdapResult().setErrorMessage( msg );
-
- if ( ( e.getResolvedName() != null ) &&
- ( ( code == ResultCodeEnum.NOSUCHOBJECT ) ||
- ( code == ResultCodeEnum.ALIASPROBLEM ) ||
- ( code == ResultCodeEnum.INVALIDDNSYNTAX ) ||
- ( code == ResultCodeEnum.ALIASDEREFERENCINGPROBLEM ) ) )
- {
- resp.getLdapResult().setMatchedDn( e.getResolvedName().toString() );
- }
-
- return resp;
- }
-
- class SearchResponseIterator implements Iterator
- {
- private final SearchRequest req;
- private final NamingEnumeration underlying;
- private SearchResponseDone respDone;
- private boolean done = false;
- private Object prefetched;
-
- /**
- * Creates a search response iterator for the resulting enumeration
- * over a search request.
- *
- * @param req the search request to generate responses to
- * @param underlying the underlying JNDI enumeration containing SearchResults
- */
- public SearchResponseIterator( SearchRequest req,
- NamingEnumeration underlying )
- {
- this.req = req;
- this.underlying = underlying;
-
- try
- {
- if( underlying.hasMore() )
- {
- SearchResult result = ( SearchResult ) underlying.next();
-
- /*
- * Now we have to build the prefetched object from the 'result'
- * local variable for the following call to next()
- */
- Attribute ref = result.getAttributes().get( "ref" );
-
- if( ref == null || ref.size() > 0 )
- {
- SearchResponseEntry respEntry;
- respEntry = new SearchResponseEntryImpl( req.getMessageId() );
- respEntry.setAttributes( result.getAttributes() );
- respEntry.setObjectName( result.getName() );
- prefetched = respEntry;
- }
- else
- {
- SearchResponseReference respRef;
- respRef = new SearchResponseReferenceImpl( req.getMessageId() );
- respRef.setReferral( new ReferralImpl( respRef ) );
-
- for( int ii = 0; ii < ref.size(); ii ++ )
- {
- String url;
-
- try
- {
- url = ( String ) ref.get( ii );
- respRef.getReferral().addLdapUrl( url );
- }
- catch( NamingException e )
- {
- try
- {
- underlying.close();
- }
- catch( Throwable t )
- {
- }
-
- prefetched = null;
- respDone = getResponse( req, e );
- }
- }
-
- prefetched = respRef;
- }
- }
- }
- catch( NamingException e )
- {
- try
- {
- this.underlying.close();
- }
- catch( Exception e2 )
- {
- }
-
- respDone = getResponse( req, e );
- }
- }
-
- public boolean hasNext()
- {
- return !done;
- }
-
- public Object next()
- {
- Object next = prefetched;
- SearchResult result = null;
-
- // if we're done we got nothing to give back
- if( done )
- {
- throw new NoSuchElementException();
- }
-
- // if respDone has been assembled this is our last object to return
- if( respDone != null )
- {
- done = true;
- return respDone;
- }
-
- /*
- * If we have gotten this far then we have a valid next entry
- * or referral to return from this call in the 'next' variable.
- */
- try
- {
- /*
- * If we have more results from the underlying cursorr then
- * we just set the result and build the response object below.
- */
- if( underlying.hasMore() )
- {
- result = ( SearchResult ) underlying.next();
- }
- else
- {
- try
- {
- underlying.close();
- }
- catch( Throwable t )
- {
- }
-
- respDone = new SearchResponseDoneImpl( req.getMessageId() );
- respDone.setLdapResult( new LdapResultImpl( respDone ) );
- respDone.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
- prefetched = null;
- return next;
- }
- }
- catch( NamingException e )
- {
- try
- {
- underlying.close();
- }
- catch( Throwable t )
- {
- }
-
- prefetched = null;
- respDone = getResponse( req, e );
- return next;
- }
-
- /*
- * Now we have to build the prefetched object from the 'result'
- * local variable for the following call to next()
- */
- Attribute ref = result.getAttributes().get( "ref" );
-
- if( ref == null || ref.size() > 0 )
- {
- SearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
- respEntry.setAttributes( result.getAttributes() );
- respEntry.setObjectName( result.getName() );
- prefetched = respEntry;
- }
- else
- {
- SearchResponseReference respRef = new SearchResponseReferenceImpl( req.getMessageId() );
- respRef.setReferral( new ReferralImpl( respRef ) );
-
- for( int ii = 0; ii < ref.size(); ii ++ )
- {
- String url;
-
- try
- {
- url = ( String ) ref.get( ii );
- respRef.getReferral().addLdapUrl( url );
- }
- catch( NamingException e )
- {
- try
- {
- underlying.close();
- }
- catch( Throwable t )
- {
- }
-
- prefetched = null;
- respDone = getResponse( req, e );
- return next;
- }
- }
-
- prefetched = respRef;
- }
-
- return next;
- }
-
- /**
- * Unsupported so it throws an exception.
- *
- * @throws UnsupportedOperationException
- */
- public void remove()
- {
- throw new UnsupportedOperationException();
- }
- }
-
-
- private PersistentSearchControl getPersistentSearchControl( SearchRequest req )
- {
- Iterator list = req.getControls().iterator();
- while ( list.hasNext() )
- {
- Control control = ( Control ) list.next();
- if ( control.getID().equals( "2.16.840.1.113730.3.4.3" ) )
- {
- return ( PersistentSearchControl ) control;
- }
- }
-
- return null;
- }
-
-
- class PersistentSearchHandler implements ObjectChangeListener, NamespaceChangeListener
- {
- final ServerLdapContext ctx;
- final IoSession session;
- final SearchRequest req;
- final PersistentSearchControl control;
-
-
- PersistentSearchHandler( ServerLdapContext ctx, IoSession session, SearchRequest req )
- {
- this.session = session;
- this.req = req;
- this.ctx = ctx;
- this.req.put( "PersistentSearchHandler", this );
- this.control = getPersistentSearchControl( req );
- }
-
-
- public void abandon() throws NamingException
- {
- // must abandon the operation and send response done with success
- ctx.removeNamingListener( this );
-
- // remove from outstanding map
- SessionRegistry.getSingleton().removeOutstandingRequest( session, new Integer( req.getMessageId() ) );
-
- // send successful response back to client
- SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
- resp.setLdapResult( new LdapResultImpl( resp ) );
- resp.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
- resp.getLdapResult().setMatchedDn( req.getBase() );
- session.write( resp );
- }
-
-
- 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 );
- }
-
- 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() );
- }
-
- SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
- ResultCodeEnum code = null;
-
- if( evt.getException() instanceof LdapException )
- {
- code = ( ( LdapException ) evt.getException() ).getResultCode();
- }
- else
- {
- code = ResultCodeEnum.getBestEstimate( evt.getException(), req.getType() );
- }
-
- resp.setLdapResult( new LdapResultImpl( resp ) );
- resp.getLdapResult().setResultCode( code );
- resp.getLdapResult().setErrorMessage( msg );
-
- if ( ( evt.getException().getResolvedName() != null ) &&
- ( ( code == ResultCodeEnum.NOSUCHOBJECT ) ||
- ( code == ResultCodeEnum.ALIASPROBLEM ) ||
- ( code == ResultCodeEnum.INVALIDDNSYNTAX ) ||
- ( code == ResultCodeEnum.ALIASDEREFERENCINGPROBLEM ) ) )
- {
- resp.getLdapResult().setMatchedDn( evt.getException().getResolvedName().toString() );
- }
-
- session.write( resp );
- }
-
-
- 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 );
}
}
}
Added: directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchResponseIterator.java
URL: http://svn.apache.org/viewcvs/directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchResponseIterator.java?rev=366439&view=auto
==============================================================================
--- directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchResponseIterator.java (added)
+++ directory/trunk/ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchResponseIterator.java Thu Jan 5 22:43:39 2006
@@ -0,0 +1,306 @@
+/*
+ *
+ * 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.ldap.server.protocol.support;
+
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.SearchResult;
+
+import org.apache.ldap.common.exception.LdapException;
+import org.apache.ldap.common.message.LdapResultImpl;
+import org.apache.ldap.common.message.ReferralImpl;
+import org.apache.ldap.common.message.ResultCodeEnum;
+import org.apache.ldap.common.message.SearchRequest;
+import org.apache.ldap.common.message.SearchResponseDone;
+import org.apache.ldap.common.message.SearchResponseDoneImpl;
+import org.apache.ldap.common.message.SearchResponseEntry;
+import org.apache.ldap.common.message.SearchResponseEntryImpl;
+import org.apache.ldap.common.message.SearchResponseReference;
+import org.apache.ldap.common.message.SearchResponseReferenceImpl;
+import org.apache.ldap.common.util.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A iterator which wraps a search result returning naming enumeration to return
+ * search responses.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+class SearchResponseIterator implements Iterator
+{
+ private static final Logger log = LoggerFactory.getLogger( SearchResponseIterator.class );
+ private final SearchRequest req;
+ private final NamingEnumeration underlying;
+ private SearchResponseDone respDone;
+ private boolean done = false;
+ private Object prefetched;
+
+ /**
+ * Creates a search response iterator for the resulting enumeration
+ * over a search request.
+ *
+ * @param req the search request to generate responses to
+ * @param underlying the underlying JNDI enumeration containing SearchResults
+ * @param handler TODO
+ */
+ public SearchResponseIterator( SearchRequest req, NamingEnumeration underlying )
+ {
+ this.req = req;
+ this.underlying = underlying;
+
+ try
+ {
+ if( underlying.hasMore() )
+ {
+ SearchResult result = ( SearchResult ) underlying.next();
+
+ /*
+ * Now we have to build the prefetched object from the 'result'
+ * local variable for the following call to next()
+ */
+ Attribute ref = result.getAttributes().get( "ref" );
+
+ if( ref == null || ref.size() > 0 )
+ {
+ SearchResponseEntry respEntry;
+ respEntry = new SearchResponseEntryImpl( req.getMessageId() );
+ respEntry.setAttributes( result.getAttributes() );
+ respEntry.setObjectName( result.getName() );
+ prefetched = respEntry;
+ }
+ else
+ {
+ SearchResponseReference respRef;
+ respRef = new SearchResponseReferenceImpl( req.getMessageId() );
+ respRef.setReferral( new ReferralImpl( respRef ) );
+
+ for( int ii = 0; ii < ref.size(); ii ++ )
+ {
+ String url;
+
+ try
+ {
+ url = ( String ) ref.get( ii );
+ respRef.getReferral().addLdapUrl( url );
+ }
+ catch( NamingException e )
+ {
+ try
+ {
+ underlying.close();
+ }
+ catch( Throwable t )
+ {
+ }
+
+ prefetched = null;
+ respDone = getResponse( req, e );
+ }
+ }
+
+ prefetched = respRef;
+ }
+ }
+ }
+ catch( NamingException e )
+ {
+ try
+ {
+ this.underlying.close();
+ }
+ catch( Exception e2 )
+ {
+ }
+
+ respDone = getResponse( req, e );
+ }
+ }
+
+ public boolean hasNext()
+ {
+ return !done;
+ }
+
+ public Object next()
+ {
+ Object next = prefetched;
+ SearchResult result = null;
+
+ // if we're done we got nothing to give back
+ if( done )
+ {
+ throw new NoSuchElementException();
+ }
+
+ // if respDone has been assembled this is our last object to return
+ if( respDone != null )
+ {
+ done = true;
+ return respDone;
+ }
+
+ /*
+ * If we have gotten this far then we have a valid next entry
+ * or referral to return from this call in the 'next' variable.
+ */
+ try
+ {
+ /*
+ * If we have more results from the underlying cursorr then
+ * we just set the result and build the response object below.
+ */
+ if( underlying.hasMore() )
+ {
+ result = ( SearchResult ) underlying.next();
+ }
+ else
+ {
+ try
+ {
+ underlying.close();
+ }
+ catch( Throwable t )
+ {
+ }
+
+ respDone = new SearchResponseDoneImpl( req.getMessageId() );
+ respDone.setLdapResult( new LdapResultImpl( respDone ) );
+ respDone.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
+ prefetched = null;
+ return next;
+ }
+ }
+ catch( NamingException e )
+ {
+ try
+ {
+ underlying.close();
+ }
+ catch( Throwable t )
+ {
+ }
+
+ prefetched = null;
+ respDone = getResponse( req, e );
+ return next;
+ }
+
+ /*
+ * Now we have to build the prefetched object from the 'result'
+ * local variable for the following call to next()
+ */
+ Attribute ref = result.getAttributes().get( "ref" );
+
+ if( ref == null || ref.size() > 0 )
+ {
+ SearchResponseEntry respEntry = new SearchResponseEntryImpl( req.getMessageId() );
+ respEntry.setAttributes( result.getAttributes() );
+ respEntry.setObjectName( result.getName() );
+ prefetched = respEntry;
+ }
+ else
+ {
+ SearchResponseReference respRef = new SearchResponseReferenceImpl( req.getMessageId() );
+ respRef.setReferral( new ReferralImpl( respRef ) );
+
+ for( int ii = 0; ii < ref.size(); ii ++ )
+ {
+ String url;
+
+ try
+ {
+ url = ( String ) ref.get( ii );
+ respRef.getReferral().addLdapUrl( url );
+ }
+ catch( NamingException e )
+ {
+ try
+ {
+ underlying.close();
+ }
+ catch( Throwable t )
+ {
+ }
+
+ prefetched = null;
+ respDone = getResponse( req, e );
+ return next;
+ }
+ }
+
+ prefetched = respRef;
+ }
+
+ return next;
+ }
+
+
+ /**
+ * Unsupported so it throws an exception.
+ *
+ * @throws UnsupportedOperationException
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+ SearchResponseDone getResponse( SearchRequest req, NamingException e )
+ {
+ String msg = "failed on search operation";
+
+ if ( log.isDebugEnabled() )
+ {
+ msg += ":\n" + req + ":\n" + ExceptionUtils.getStackTrace( e );
+ }
+
+ SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
+ ResultCodeEnum code = null;
+
+ if( e instanceof LdapException )
+ {
+ code = ( ( LdapException ) e ).getResultCode();
+ }
+ else
+ {
+ code = ResultCodeEnum.getBestEstimate( e, req.getType() );
+ }
+
+ resp.setLdapResult( new LdapResultImpl( resp ) );
+ resp.getLdapResult().setResultCode( code );
+ resp.getLdapResult().setErrorMessage( msg );
+
+ if ( ( e.getResolvedName() != null ) &&
+ ( ( code == ResultCodeEnum.NOSUCHOBJECT ) ||
+ ( code == ResultCodeEnum.ALIASPROBLEM ) ||
+ ( code == ResultCodeEnum.INVALIDDNSYNTAX ) ||
+ ( code == ResultCodeEnum.ALIASDEREFERENCINGPROBLEM ) ) )
+ {
+ resp.getLdapResult().setMatchedDn( e.getResolvedName().toString() );
+ }
+
+ return resp;
+ }
+}
\ No newline at end of file