From commits-return-7638-apmail-directory-commits-archive=directory.apache.org@directory.apache.org Fri Jan 06 06:44:16 2006 Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 20429 invoked from network); 6 Jan 2006 06:44:16 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 6 Jan 2006 06:44:16 -0000 Received: (qmail 41058 invoked by uid 500); 6 Jan 2006 06:44:15 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 41008 invoked by uid 500); 6 Jan 2006 06:44:15 -0000 Mailing-List: contact commits-help@directory.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@directory.apache.org Delivered-To: mailing list commits@directory.apache.org Received: (qmail 40997 invoked by uid 99); 6 Jan 2006 06:44:14 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jan 2006 22:44:14 -0800 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.29) with SMTP; Thu, 05 Jan 2006 22:44:12 -0800 Received: (qmail 20300 invoked by uid 65534); 6 Jan 2006 06:43:52 -0000 Message-ID: <20060106064352.20299.qmail@minotaur.apache.org> Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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/ Date: Fri, 06 Jan 2006 06:43:49 -0000 To: commits@directory.apache.org From: akarasulu@apache.org X-Mailer: svnmailer-1.0.5 X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N 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 Apache Directory Project + * @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 Apache Directory Project + * @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 Apache Directory Project + * @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