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:[email protected]">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 [EMAIL PROTECTED] LdapSession} to construct the node
for
+ * @return the [EMAIL PROTECTED] 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 [EMAIL PROTECTED] SearchRequest} issued
+ * @param session the [EMAIL PROTECTED] LdapSession} on which search was
requested
+ * @param cursor the [EMAIL PROTECTED] 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:[email protected]">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;
+ }
+}
+
+