Sure will do that if power stays on long enough.  Power cut out while I was working on this yesterday.

On Thu, Aug 21, 2008 at 8:44 AM, Emmanuel Lecharny <elecharny@gmail.com> wrote:
Hi Alex,

can you add the minimum Javadoc needed for the public methods and for the fields of the SearchTimeLimitingMonitor class ?

Thanks !

akarasulu@apache.org wrote:
Author: akarasulu
Date: Wed Aug 20 21:38:50 2008
New Revision: 687555

URL: http://svn.apache.org/viewvc?rev=687555&view=rev
Log:
implemented search size limits handling using Cursor ClosureMonitor technique and added abandon handling

Added:
   directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchAbandonListener.java
   directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchTimeLimitingMonitor.java
Modified:
   directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/LdapServer.java
   directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/LdapRequestHandler.java
   directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchHandler.java

Modified: directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/LdapServer.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/LdapServer.java?rev=687555&r1=687554&r2=687555&view=diff
==============================================================================
--- directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/LdapServer.java (original)
+++ directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/LdapServer.java Wed Aug 20 21:38:50 2008
@@ -120,6 +120,12 @@
    /** The default maximum time limit. */
    private static final int MAX_TIME_LIMIT_DEFAULT = 10000;
 +    /** Value (0) for configuration where size limit is unlimited. */
+    public static final int NO_SIZE_LIMIT = 0;
+
+    /** Value (0) for configuration where time limit is unlimited. */
+    public static final int NO_TIME_LIMIT = 0;
+
    /** The default service pid. */
    private static final String SERVICE_PID_DEFAULT = "org.apache.directory.server.ldap";
 
Modified: directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/LdapRequestHandler.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/LdapRequestHandler.java?rev=687555&r1=687554&r2=687555&view=diff
==============================================================================
--- directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/LdapRequestHandler.java (original)
+++ directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/LdapRequestHandler.java Wed Aug 20 21:38:50 2008
@@ -117,6 +117,10 @@
    public final void messageReceived( IoSession session, T message ) throws Exception
    {
        LdapSession ldapSession = ldapServer.getLdapSession( session );
+        +        // TODO - session you get from LdapServer should have the ldapServer +        // member already set no?  Should remove these lines where ever they
+        // may be if that's the case.
        ldapSession.setLdapServer( ldapServer );
                // protect against insecure conns when confidentiality is required
Added: directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchAbandonListener.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchAbandonListener.java?rev=687555&view=auto
==============================================================================
--- directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchAbandonListener.java (added)
+++ directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchAbandonListener.java Wed Aug 20 21:38:50 2008
@@ -0,0 +1,102 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *   or more contributor license agreements.  See the NOTICE file
+ *   distributed with this work for additional information
+ *   regarding copyright ownership.  The ASF licenses this file
+ *   to you under the Apache License, Version 2.0 (the
+ *   "License"); you may not use this file except in compliance
+ *   with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing,
+ *   software distributed under the License is distributed on an
+ *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *   KIND, either express or implied.  See the License for the
+ *   specific language governing permissions and limitations
+ *   under the License.
+ *
+ */
+package org.apache.directory.server.ldap.handlers;
+
+
+import org.apache.directory.server.core.event.DirectoryListener;
+import org.apache.directory.server.core.filtering.EntryFilteringCursor;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.shared.ldap.exception.OperationAbandonedException;
+import org.apache.directory.shared.ldap.message.AbandonListener;
+import org.apache.directory.shared.ldap.message.AbandonableRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An AbandonListener implementation which closes an associated cursor or + * removes a DirectoryListener.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class SearchAbandonListener implements AbandonListener
+{
+    private static final Logger LOG = LoggerFactory.getLogger( SearchAbandonListener.class );
+    private final LdapServer ldapServer;
+    private EntryFilteringCursor cursor;
+    private DirectoryListener listener;
+    +    +    public SearchAbandonListener( LdapServer ldapServer, EntryFilteringCursor cursor, DirectoryListener listener )
+    {
+        if ( ldapServer == null )
+        {
+            throw new NullPointerException( "ldapServer" );
+        }
+        +        this.ldapServer = ldapServer;
+        this.cursor = cursor;
+        this.listener = listener;
+    }
+    +    +    public SearchAbandonListener( LdapServer ldapServer, DirectoryListener listener )
+    {
+        this ( ldapServer, null, listener );
+    }
+    +    +    public SearchAbandonListener( LdapServer ldapServer, EntryFilteringCursor cursor )
+    {
+        this ( ldapServer, cursor, null );
+    }
+    +    +    public void requestAbandoned( AbandonableRequest req )
+    {
+        if ( listener != null )
+        {
+            ldapServer.getDirectoryService().getEventService().removeListener( listener );
+        }
+
+        try
+        {
+            if ( cursor != null )
+            {
+                /*
+                 * When this method is called due to an abandon request it +                 * will close the cursor but other threads processing the +                 * search will get an OperationAbandonedException which as
+                 * seen below will make sure the proper handling is +                 * performed.
+                 */
+                cursor.close( new OperationAbandonedException() );
+            }
+        }
+        catch ( Exception e )
+        {
+            LOG.error( "Failed to close the search cursor for message {} on abandon request.", +                req.getMessageId(), e );
+        }
+    }
+}
+
+

Modified: directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchHandler.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchHandler.java?rev=687555&r1=687554&r2=687555&view=diff
==============================================================================
--- directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchHandler.java (original)
+++ directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchHandler.java Wed Aug 20 21:38:50 2008
@@ -20,12 +20,15 @@
 package org.apache.directory.server.ldap.handlers;
  +import java.util.concurrent.TimeUnit;
+
 import org.apache.directory.server.core.entry.ClonedServerEntry;
 import org.apache.directory.server.core.entry.ServerEntryUtils;
 import org.apache.directory.server.core.entry.ServerStringValue;
 import org.apache.directory.server.core.event.EventType;
 import org.apache.directory.server.core.event.NotificationCriteria;
 import org.apache.directory.server.core.filtering.EntryFilteringCursor;
+import org.apache.directory.server.ldap.LdapServer;
 import org.apache.directory.server.ldap.LdapSession;
 import org.apache.directory.shared.ldap.codec.util.LdapURL;
 import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
@@ -36,7 +39,6 @@
 import org.apache.directory.shared.ldap.filter.EqualityNode;
 import org.apache.directory.shared.ldap.filter.OrNode;
 import org.apache.directory.shared.ldap.filter.PresenceNode;
-import org.apache.directory.shared.ldap.message.AbandonListener;
 import org.apache.directory.shared.ldap.message.LdapResult;
 import org.apache.directory.shared.ldap.message.ManageDsaITControl;
 import org.apache.directory.shared.ldap.message.PersistentSearchControl;
@@ -70,10 +72,20 @@
      /** Speedup for logs */
    private static final boolean IS_DEBUG = LOG.isDebugEnabled();
+
+    /** cached to save redundant lookups into registries */ +    private AttributeType objectClassAttributeType;
    -    AttributeType objectClassAttributeType;
    -    private EqualityNode<String> getOcIsReferralAssertion( LdapSession session ) throws Exception
+    /**
+     * Constructs a new filter EqualityNode asserting that a candidate +     * objectClass is a referral.
+     *
+     * @param session the {@link LdapSession} to construct the node for
+     * @return the {@link EqualityNode} (objectClass=referral) non-normalized
+     * @throws Exception in the highly unlikely event of schema related failures
+     */
+    private EqualityNode<String> newIsReferralEqualityNode( LdapSession session ) throws Exception
    {
        if ( objectClassAttributeType == null )
        {
@@ -81,14 +93,23 @@
                .getAttributeTypeRegistry().lookup( SchemaConstants.OBJECT_CLASS_AT );
        }
        -        EqualityNode<String> ocIsReferral = new EqualityNode<String>( -            SchemaConstants.OBJECT_CLASS_AT,
+        EqualityNode<String> ocIsReferral = new EqualityNode<String>( SchemaConstants.OBJECT_CLASS_AT,
            new ServerStringValue( objectClassAttributeType, SchemaConstants.REFERRAL_OC ) );
                return ocIsReferral;
    }
        +    /**
+     * Handles search requests containing the persistent search control but +     * delegates to doSimpleSearch() if the changesOnly parameter of the +     * control is set to false.
+     *
+     * @param session the LdapSession for which this search is conducted +     * @param req the search request containing the persistent search control
+     * @param psearchControl the persistent search control extracted
+     * @throws Exception if failures are encountered while searching
+     */
    private void handlePersistentSearch( LdapSession session, SearchRequest req,         PersistentSearchControl psearchControl ) throws Exception     {
@@ -108,7 +129,12 @@
            }
        }
 -        // now we process entries for ever as they change
+        if ( req.isAbandoned() )
+        {
+            return;
+        }
+        +        // now we process entries forever as they change
        PersistentSearchListener handler = new PersistentSearchListener( session, req );
                // compose notification criteria and add the listener to the event @@ -121,15 +147,17 @@
        criteria.setScope( req.getScope() );
        criteria.setEventMask( EventType.getEventTypes( psearchControl.getChangeTypes() ) );
        getLdapServer().getDirectoryService().getEventService().addListener( handler, criteria );
+        req.addAbandonListener( new SearchAbandonListener( ldapServer, handler ) );
        return;
    }
            /**
-     * Deal with RootDE search. -     * @param session
-     * @param req
-     * @throws Exception
+     * Handles search requests on the RootDSE. +     * +     * @param session the LdapSession for which this search is conducted +     * @param req the search request on the RootDSE
+     * @throws Exception if failures are encountered while searching
     */
    private void handleRootDseSearch( LdapSession session, SearchRequest req ) throws Exception
    {
@@ -147,9 +175,9 @@
            {
               if ( hasRootDSE )
               {
-                       // This is an error ! We should never find more
-                       // than one rootDSE !
-                       // TODO : handle this error
+                       // This is an error ! We should never find more than one rootDSE !
+                   LOG.error( "Got back more than one entry for search on RootDSE which means " +
+                               "Cursor is not functioning properly!" );
               }
               else
               {
@@ -181,6 +209,66 @@
            /**
+     * Based on the server maximum time limits configured for search and the +     * requested time limits this method determines if at all to replace the +     * default ClosureMonitor of the result set Cursor with one that closes
+     * the Cursor when either server mandated or request mandated time limits +     * are reached.
+     *
+     * @param req the {@link SearchRequest} issued
+     * @param session the {@link LdapSession} on which search was requested
+     * @param cursor the {@link EntryFilteringCursor} over the search results
+     */
+    private void setTimeLimitsOnCursor( SearchRequest req, LdapSession session, final EntryFilteringCursor cursor )
+    {
+        // Don't bother setting time limits for administrators
+        if ( session.getCoreSession().isAnAdministrator() && req.getTimeLimit() == 0 )
+        {
+            return;
+        }
+        +        /*
+         * Non administrator based searches are limited by time if the server +         * has been configured with unlimited time and the request specifies +         * unlimited search time
+         */
+        if ( ldapServer.getMaxTimeLimit() == LdapServer.NO_TIME_LIMIT && req.getTimeLimit() == 0 )
+        {
+            return;
+        }
+        +        /*
+         * If the non-administrator user specifies unlimited time but the server +         * is configured to limit the search time then we limit by the max time +         * allowed by the configuration +         */
+        if ( req.getTimeLimit() == 0 )
+        {
+            cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) );
+            return;
+        }
+        +        /*
+         * If the non-administrative user specifies a time limit equal to or +         * less than the maximum limit configured in the server then we +         * constrain search by the amount specified in the request
+         */
+        if ( ldapServer.getMaxSizeLimit() >= req.getTimeLimit() )
+        {
+            cursor.setClosureMonitor( new SearchTimeLimitingMonitor( req.getTimeLimit(), TimeUnit.SECONDS ) );
+            return;
+        }
+
+        /*
+         * Here the non-administrative user's requested time limit is greater +         * than what the server's configured maximum limit allows so we limit
+         * the search to the configured limit
+         */
+        cursor.setClosureMonitor( new SearchTimeLimitingMonitor( ldapServer.getMaxTimeLimit(), TimeUnit.SECONDS ) );
+    }
+    +    +    /**
     * Conducts a simple search across the result set returning each entry      * back except for the search response done.  This is calculated but not
     * returned so the persistent search mechanism can leverage this method
@@ -191,7 +279,8 @@
     * @return the result done      * @throws Exception if there are failures while processing the request
     */
-    private SearchResponseDone doSimpleSearch( LdapSession session, SearchRequest req ) throws Exception
+    private SearchResponseDone doSimpleSearch( LdapSession session, SearchRequest req ) +        throws Exception
    {
        /*
         * Iterate through all search results building and sending back responses
@@ -202,13 +291,9 @@
        try
        {
            cursor = session.getCoreSession().search( req );
+            req.addAbandonListener( new SearchAbandonListener( ldapServer, cursor ) );
+            setTimeLimitsOnCursor( req, session, cursor );
            -            // TODO - fix this (need to make Cursors abandonable)
-            if ( cursor instanceof AbandonListener )
-            {
-                req.addAbandonListener( ( AbandonListener ) cursor );
-            }
-                 // Position the cursor at the beginning
            cursor.beforeFirst();
            @@ -361,7 +446,7 @@
        }
          // using varags to add two expressions to an OR node -        req.setFilter( new OrNode( req.getFilter(), getOcIsReferralAssertion( session ) ) );
+        req.setFilter( new OrNode( req.getFilter(), newIsReferralEqualityNode( session ) ) );
    }
       
Added: directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchTimeLimitingMonitor.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchTimeLimitingMonitor.java?rev=687555&view=auto
==============================================================================
--- directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchTimeLimitingMonitor.java (added)
+++ directory/apacheds/trunk/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/SearchTimeLimitingMonitor.java Wed Aug 20 21:38:50 2008
@@ -0,0 +1,149 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *   or more contributor license agreements.  See the NOTICE file
+ *   distributed with this work for additional information
+ *   regarding copyright ownership.  The ASF licenses this file
+ *   to you under the Apache License, Version 2.0 (the
+ *   "License"); you may not use this file except in compliance
+ *   with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing,
+ *   software distributed under the License is distributed on an
+ *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *   KIND, either express or implied.  See the License for the
+ *   specific language governing permissions and limitations
+ *   under the License.
+ *
+ */
+package org.apache.directory.server.ldap.handlers;
+
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.directory.server.core.cursor.ClosureMonitor;
+import org.apache.directory.server.core.cursor.CursorClosedException;
+import org.apache.directory.shared.ldap.exception.LdapTimeLimitExceededException;
+
+
+/**
+ * A ClosureMonitor implementation which takes into account a time limit.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class SearchTimeLimitingMonitor implements ClosureMonitor
+{
+    private final long startTime = System.currentTimeMillis();
+    private final long millisToLive;
+    +    private boolean closed;
+    private Exception cause;
+    +    +    public SearchTimeLimitingMonitor( long timeToLive, TimeUnit unit )
+    {
+        switch ( unit )
+        {
+            case MICROSECONDS:
+                this.millisToLive = timeToLive / 1000;
+                break;
+            case MILLISECONDS:
+                this.millisToLive = timeToLive;
+                break;
+            case SECONDS:
+                this.millisToLive = timeToLive * 1000;
+                break;
+            case MINUTES:
+                this.millisToLive = timeToLive * 60000;
+                break;
+            default:
+                throw new IllegalStateException( "TimeUnit not supported: " + unit );
+        }
+    }
+
+    +    public void checkNotClosed() throws Exception
+    {
+        if ( System.currentTimeMillis() > startTime + millisToLive )
+        {
+            // state check needed to "try" not to overwrite exception (lack of +            // synchronization may still allow overwriting but who cares that +            // much
+            if ( ! closed )
+            {
+                // not going to sync because who cares if it takes a little +                // longer to stop but we need to set cause before toggling +                // closed state or else check for closure can throw null cause +                cause = new LdapTimeLimitExceededException();
+                closed = true;
+            }
+        }
+        +        if ( closed )
+        {
+            throw cause;
+        }
+    }
+
+    +    public void close()
+    {
+        if ( ! closed )
+        {
+            // not going to sync because who cares if it takes a little longer +            // to stop but we need to set cause before toggling closed state +            // or else check for closure can throw null cause +            cause = new CursorClosedException();
+            closed = true;
+        }
+    }
+
+    +    public void close( String cause )
+    {
+        if ( ! closed )
+        {
+            // not going to sync because who cares if it takes a little longer +            // to stop but we need to set cause before toggling closed state +            // or else check for closure can throw null cause +            this.cause = new CursorClosedException( cause );
+            closed = true;
+        }
+    }
+
+    +    public void close( Exception cause )
+    {
+        if ( ! closed )
+        {
+            // not going to sync because who cares if it takes a little longer +            // to stop but we need to set cause before toggling closed state +            // or else check for closure can throw null cause +            this.cause = cause;
+            closed = true;
+        }
+    }
+
+    +    public Exception getCause()
+    {
+        return cause;
+    }
+
+    +    public boolean isClosed()
+    {
+        if ( System.currentTimeMillis() > startTime + millisToLive )
+        {
+            // set cause first always
+            cause = new LdapTimeLimitExceededException();
+            closed = true;
+        }
+        +        return closed;
+    }
+}
+
+



 


--
--
cordialement, regards,
Emmanuel Lécharny
www.iktek.com
directory.apache.org