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 04:26:38 UTC

svn commit: r366404 - in /directory/trunk: apacheds-server-unit/src/test/java/org/apache/ldap/server/PersistentSearchTest.java ldap-protocol/src/main/java/org/apache/ldap/server/protocol/support/SearchHandler.java

Author: akarasulu
Date: Thu Jan  5 19:26:32 2006
New Revision: 366404

URL: http://svn.apache.org/viewcvs?rev=366404&view=rev
Log:
changes ...

 o implemented the returnEC field in the server so EntryChangeControls are sent
   in responses if this field is set to true
 o implemented selective notifications based on the changeTypes filed of the 
   PersistentSearchControl
 o added test cases to test defaults, runs with returnEC set to true, and 
   tests confirming correct operation of the changeTypes field in PSearchCOntrol


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

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=366404&r1=366403&r2=366404&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 19:26:32 2006
@@ -26,9 +26,13 @@
 import javax.naming.directory.DirContext;
 import javax.naming.directory.SearchResult;
 import javax.naming.ldap.Control;
+import javax.naming.ldap.HasControls;
 import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapContext;
 
+import org.apache.ldap.common.codec.search.controls.ChangeType;
+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;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -105,6 +109,9 @@
     }
 
 
+    /**
+     * Shows correct notifications for modify(4) changes.
+     */
     public void testPsearchModify() throws Exception
     {
         PSearchListener listener = new PSearchListener();
@@ -131,9 +138,14 @@
         }
         
         assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
     }
 
 
+    /**
+     * Shows correct notifications for moddn(8) changes.
+     */
     public void testPsearchModifyDn() throws Exception
     {
         PSearchListener listener = new PSearchListener();
@@ -160,9 +172,14 @@
         }
         
         assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
     }
 
     
+    /**
+     * Shows correct notifications for delete(2) changes.
+     */
     public void testPsearchDelete() throws Exception
     {
         PSearchListener listener = new PSearchListener();
@@ -189,9 +206,14 @@
         }
         
         assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
     }
 
     
+    /**
+     * Shows correct notifications for add(1) changes.
+     */
     public void testPsearchAdd() throws Exception
     {
         PSearchListener listener = new PSearchListener();
@@ -218,17 +240,252 @@
         }
         
         assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
     }
+
+
+    /**
+     * Shows correct notifications for modify(4) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchModifyWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener, "PSearchListener" );
+        t.start();
         
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                System.out.println( "PSearchListener thread not dead yet" );
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
+    }
+
+
+    /**
+     * Shows correct notifications for moddn(8) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchModifyDnWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.rename( RDN, "cn=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;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODDN );
+        assertEquals( ( RDN + ",ou=system" ).toLowerCase(), listener.result.control.getPreviousDn().toLowerCase() );
+    }
+
+    
+    /**
+     * Shows correct notifications for delete(2) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchDeleteWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.destroySubcontext( RDN );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                System.out.println( "PSearchListener thread not dead yet" );
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.DELETE );
+    }
+
     
+    /**
+     * Shows correct notifications for add(1) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchAddWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        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;
+            }
+        }
+        
+        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 );
+    }
+
+    
+    /**
+     * Shows correct notifications for only add(1) and modify(4) registered changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchAddModifyEnabledWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        control.setChangeTypes( ChangeType.ADD_VALUE );
+        control.enableNotification( ChangeType.MODIFY );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        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;
+            }
+        }
+        
+        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 );
+        listener.result = null;
+        t = new Thread( listener );
+        t.start();
+        
+        ctx.destroySubcontext( "cn=Jack Black" );
+        
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                System.out.println( "PSearchListener thread not dead yet" );
+                break;
+            }
+        }
+
+        assertNull( listener.result );
+
+        // thread is still waiting for notifications try a modify
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                System.out.println( "PSearchListener thread not dead yet" );
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
+    }
+    
+
     class PSearchListener implements Runnable
     {
         boolean isReady = false;
-        SearchResult result;
+        PSearchNotification result;
+        final PersistentSearchControl control;
+
+        PSearchListener() { control = new PersistentSearchControl(); }
+        PSearchListener( PersistentSearchControl control ) { this.control = control; }
         
         public void run()
         {
-            PersistentSearchControl control = new PersistentSearchControl();
             control.setCritical( true );
             Control[] ctxCtls = new Control[] { control };
             
@@ -237,10 +494,30 @@
                 ctx.setRequestControls( ctxCtls );
                 isReady = true;
                 NamingEnumeration list = ctx.search( "", "objectClass=*", null );
+                EntryChangeControl ecControl = null;
+                
                 while( list.hasMore() )
                 {
-                    result = ( SearchResult ) list.next();
-                    System.out.println( "got notifiaction for entry: " + result.getName() );
+                    Control[] controls = null;
+                    SearchResult sresult = ( SearchResult ) list.next();
+                    if ( sresult instanceof HasControls )
+                    {
+                        controls = ( ( HasControls ) sresult ).getControls();
+                        if ( controls != null )
+                        {
+                            for ( int ii = 0; ii < controls.length; ii ++ )
+                            {
+                                if ( controls[ii].getID().equals( 
+                                    org.apache.ldap.common.message.EntryChangeControl.CONTROL_ID ) )
+                                {
+                                    EntryChangeControlDecoder decoder = new EntryChangeControlDecoder();
+                                    ecControl = ( EntryChangeControl ) decoder.decode( controls[ii].getEncodedValue() );
+                                }
+                            }
+                        }
+                    }
+                    result = new PSearchNotification( sresult, ecControl );
+                    System.out.println( "got notifiaction: " + result );
                     break;
                 }
             }
@@ -248,6 +525,33 @@
             {
                 e.printStackTrace();
             }
+        }
+    }
+
+
+    class PSearchNotification extends SearchResult
+    {
+        private static final long serialVersionUID = 1L;
+        final EntryChangeControl control;
+        
+        public PSearchNotification( SearchResult result, EntryChangeControl control )
+        {
+            super( result.getName(), result.getClassName(), result.getObject(), result.getAttributes(), result.isRelative() );
+            this.control = control;
+        }
+        
+        public String toString()
+        {
+            StringBuffer buf = new StringBuffer();
+            buf.append( getName() );
+            if ( control != null )
+            {
+                buf.append( "EntryChangeControl =\n" );
+                buf.append( "    changeType   : " ).append( control.getChangeType() ).append( "\n" );
+                buf.append( "    previousDN   : " ).append( control.getPreviousDn() ).append( "\n" );
+                buf.append( "    changeNumber : " ).append( control.getChangeNumber() ).append( "\n" );
+            }
+            return buf.toString();
         }
     }
 }

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=366404&r1=366403&r2=366404&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 19:26:32 2006
@@ -15,6 +15,7 @@
  */
 package org.apache.ldap.server.protocol.support;
 
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -33,9 +34,11 @@
 import javax.naming.event.ObjectChangeListener;
 import javax.naming.ldap.LdapContext;
 
+import org.apache.ldap.common.codec.search.controls.ChangeType;
 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;
@@ -79,11 +82,11 @@
         SearchRequest req = ( SearchRequest ) request;
         NamingEnumeration list = null;
 
-        // check the attributes to see if a referral's ref attribute is included
         String[] ids = null;
         Collection retAttrs = new HashSet();
         retAttrs.addAll( req.getAttributes() );
 
+        // check the attributes to see if a referral's ref attribute is included
         if( retAttrs.size() > 0 && !retAttrs.contains( "ref" ) )
         {
             retAttrs.add( "ref" );
@@ -172,19 +175,6 @@
             PersistentSearchControl psearchControl = getPersistentSearchControl( req );
             if ( psearchControl != null )
             {
-                if ( psearchControl.isReturnECs() )
-                {
-                    SearchResponseDone resp = new SearchResponseDoneImpl( req.getMessageId() );
-                    LdapResultImpl result = new LdapResultImpl( resp );
-                    resp.setLdapResult( result );
-                    result.setResultCode( ResultCodeEnum.UNAVAILABLECRITICALEXTENSION );
-                    String msg = "EntryChangeNotification response controls not implemented yet!";
-                    log.error( msg );
-                    result.setErrorMessage( msg );
-                    session.write( resp );
-                    return;
-                }
-
                 PersistentSearchHandler handler = new PersistentSearchHandler( ctx, session, req );
                 StringBuffer buf = new StringBuffer();
                 req.getFilter().printToBuffer( buf );
@@ -541,6 +531,8 @@
         final ServerLdapContext ctx;
         final IoSession session;
         final SearchRequest req;
+        final PersistentSearchControl control;
+        
         
         PersistentSearchHandler( ServerLdapContext ctx, IoSession session, SearchRequest req ) 
         {
@@ -548,6 +540,7 @@
             this.req = req;
             this.ctx = ctx;
             this.req.put( "PersistentSearchHandler", this );
+            this.control = getPersistentSearchControl( req );
         }
         
         
@@ -645,25 +638,57 @@
 
         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 ):
-                    respEntry.setObjectName( evt.getOldBinding().getName() );
-                    respEntry.setAttributes( ( Attributes ) evt.getOldBinding().getObject() );
+                    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;