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