1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.directory.server.operations.search;
21
22
23 import java.util.ArrayList;
24 import java.util.EventObject;
25 import java.util.List;
26
27 import javax.naming.NamingEnumeration;
28 import javax.naming.directory.Attribute;
29 import javax.naming.directory.Attributes;
30 import javax.naming.directory.BasicAttribute;
31 import javax.naming.directory.BasicAttributes;
32 import javax.naming.directory.DirContext;
33 import javax.naming.directory.SearchResult;
34 import javax.naming.event.EventDirContext;
35 import javax.naming.event.NamespaceChangeListener;
36 import javax.naming.event.NamingEvent;
37 import javax.naming.event.NamingExceptionEvent;
38 import javax.naming.event.ObjectChangeListener;
39 import javax.naming.ldap.Control;
40 import javax.naming.ldap.HasControls;
41 import javax.naming.ldap.LdapContext;
42
43 import org.apache.directory.server.core.event.EventService;
44 import org.apache.directory.server.core.event.RegistrationEntry;
45 import org.apache.directory.server.core.integ.Level;
46 import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
47 import org.apache.directory.server.core.integ.annotations.CleanupLevel;
48 import org.apache.directory.server.integ.SiRunner;
49 import static org.apache.directory.server.integ.ServerIntegrationUtils.getWiredContext;
50
51 import org.apache.directory.server.ldap.LdapService;
52 import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
53 import org.apache.directory.shared.ldap.codec.search.controls.EntryChangeControlCodec;
54 import org.apache.directory.shared.ldap.codec.search.controls.EntryChangeControlDecoder;
55 import org.apache.directory.shared.ldap.message.PersistentSearchControl;
56
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import static org.junit.Assert.assertNull;
63 import static org.junit.Assert.assertEquals;
64 import static org.junit.Assert.assertNotNull;
65 import static org.junit.Assert.assertTrue;
66
67
68
69
70
71
72
73
74 @RunWith ( SiRunner.class )
75 @CleanupLevel ( Level.SUITE )
76 @ApplyLdifs( {
77
78 "dn: cn=Tori Amos,ou=system\n" +
79 "objectClass: person\n" +
80 "objectClass: top\n" +
81 "cn: Tori Amos\n" +
82 "description: an American singer-songwriter\n" +
83 "sn: Amos\n\n"
84 }
85 )
86 public class PersistentSearchIT
87 {
88 private static final Logger LOG = LoggerFactory.getLogger( PersistentSearchIT.class );
89
90 private static final String BASE = "ou=system";
91 private static final String PERSON_DESCRIPTION = "an American singer-songwriter";
92 private static final String RDN = "cn=Tori Amos";
93
94 public static LdapService ldapService;
95
96
97
98
99
100 protected Attributes getPersonAttributes( String sn, String cn )
101 {
102 Attributes attributes = new BasicAttributes( true );
103 Attribute attribute = new BasicAttribute( "objectClass" );
104 attribute.add( "top" );
105 attribute.add( "person" );
106 attributes.put( attribute );
107 attributes.put( "cn", cn );
108 attributes.put( "sn", sn );
109
110 return attributes;
111 }
112
113
114 EventDirContext ctx;
115 EventService eventService;
116 PSearchListener listener;
117 Thread t;
118
119
120 public void setUpListenerReturnECs() throws Exception
121 {
122 setUpListener( true, new PersistentSearchControl(), false );
123 }
124
125
126 public void setUpListener( boolean returnECs, PersistentSearchControl control, boolean ignoreEmptyRegistryCheck )
127 throws Exception
128 {
129 ctx = ( EventDirContext ) getWiredContext( ldapService).lookup( BASE );
130 eventService = ldapService.getDirectoryService().getEventService();
131 List<RegistrationEntry> registrationEntryList = eventService.getRegistrationEntries();
132
133 if ( ! ignoreEmptyRegistryCheck )
134 {
135 assertTrue( registrationEntryList.isEmpty() );
136 }
137
138 control.setReturnECs( returnECs );
139 listener = new PSearchListener( control );
140 t = new Thread( listener, "PSearchListener" );
141 t.start();
142
143
144 while ( eventService.getRegistrationEntries().isEmpty() )
145 {
146 Thread.sleep( 100 );
147 }
148
149 Thread.sleep( 250 );
150 }
151
152
153 public void setUpListener() throws Exception
154 {
155 ctx = ( EventDirContext ) getWiredContext( ldapService).lookup( BASE );
156 eventService = ldapService.getDirectoryService().getEventService();
157 List<RegistrationEntry> registrationEntryList = eventService.getRegistrationEntries();
158 assertTrue( registrationEntryList.isEmpty() );
159
160 listener = new PSearchListener();
161 t = new Thread( listener, "PSearchListener" );
162 t.start();
163
164
165 while ( eventService.getRegistrationEntries().isEmpty() )
166 {
167 Thread.sleep( 100 );
168 }
169
170 Thread.sleep( 250 );
171 }
172
173
174 public void tearDownListener() throws Exception
175 {
176 listener.close();
177 ctx.close();
178
179 while ( ! eventService.getRegistrationEntries().isEmpty() )
180 {
181 Thread.sleep( 100 );
182 }
183 }
184
185
186 private void waitForThreadToDie( Thread t ) throws Exception
187 {
188 long start = System.currentTimeMillis();
189 while ( t.isAlive() )
190 {
191 Thread.sleep( 200 );
192 if ( System.currentTimeMillis() - start > 1000 )
193 {
194 break;
195 }
196 }
197 }
198
199
200
201
202
203 @Test
204 public void testPsearchModify() throws Exception
205 {
206 setUpListener();
207 ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE,
208 new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
209 waitForThreadToDie( t );
210 assertNotNull( listener.result );
211 assertEquals( RDN, listener.result.getName() );
212 tearDownListener();
213 }
214
215
216
217
218
219 @Test
220 public void testPsearchModifyDn() throws Exception
221 {
222 setUpListener();
223 ctx.rename( RDN, "cn=Jack Black" );
224 waitForThreadToDie( t );
225 assertNotNull( listener.result );
226 assertEquals( "cn=Jack Black", listener.result.getName() );
227 tearDownListener();
228 }
229
230
231
232
233
234 @Test
235 public void testPsearchDelete() throws Exception
236 {
237 setUpListener();
238 ctx.destroySubcontext( RDN );
239 waitForThreadToDie( t );
240 assertNotNull( listener.result );
241 assertEquals( RDN, listener.result.getName() );
242 tearDownListener();
243 }
244
245
246
247
248
249 @Test
250 public void testPsearchAdd() throws Exception
251 {
252 setUpListener();
253 ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
254 waitForThreadToDie( t );
255 assertNotNull( listener.result );
256 assertEquals( "cn=Jack Black", listener.result.getName() );
257 tearDownListener();
258 }
259
260
261
262
263
264
265 @Test
266 public void testPsearchModifyWithEC() throws Exception
267 {
268 setUpListenerReturnECs();
269 ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, new BasicAttributes( "description", PERSON_DESCRIPTION,
270 true ) );
271 waitForThreadToDie( t );
272 assertNotNull( listener.result );
273 assertEquals( RDN, listener.result.getName() );
274 assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
275 tearDownListener();
276 }
277
278
279
280
281
282
283 @Test
284 public void testPsearchModifyDnWithEC() throws Exception
285 {
286 setUpListenerReturnECs();
287 ctx.rename( RDN, "cn=Jack Black" );
288 waitForThreadToDie( t );
289 assertNotNull( listener.result );
290 assertEquals( "cn=Jack Black", listener.result.getName() );
291 assertEquals( listener.result.control.getChangeType(), ChangeType.MODDN );
292 assertEquals( ( RDN + ",ou=system" ), listener.result.control.getPreviousDn().getUpName() );
293 tearDownListener();
294 }
295
296
297
298
299
300
301 @Test
302 public void testPsearchDeleteWithEC() throws Exception
303 {
304 setUpListenerReturnECs();
305 ctx.destroySubcontext( RDN );
306 waitForThreadToDie( t );
307 assertNotNull( listener.result );
308 assertEquals( RDN, listener.result.getName() );
309 assertEquals( listener.result.control.getChangeType(), ChangeType.DELETE );
310 tearDownListener();
311 }
312
313
314
315
316
317
318 @Test
319 public void testPsearchAddWithEC() throws Exception
320 {
321 setUpListenerReturnECs();
322 ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
323 waitForThreadToDie( t );
324 assertNotNull( listener.result );
325 assertEquals( "cn=Jack Black", listener.result.getName() );
326 assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
327 tearDownListener();
328 }
329
330
331
332
333
334
335 @Test
336 public void testPsearchAddModifyEnabledWithEC() throws Exception
337 {
338 PersistentSearchControl control = new PersistentSearchControl();
339 control.setReturnECs( true );
340 control.setChangeTypes( ChangeType.ADD_VALUE );
341 control.enableNotification( ChangeType.MODIFY );
342 setUpListener( true, control, false );
343 ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
344 waitForThreadToDie( t );
345
346 assertNotNull( listener.result );
347 assertEquals( "cn=Jack Black", listener.result.getName() );
348 assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
349 tearDownListener();
350
351 setUpListener( true, control, true );
352 ctx.destroySubcontext( "cn=Jack Black" );
353 waitForThreadToDie( t );
354 assertNull( listener.result );
355
356
357 ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, new BasicAttributes( "description", PERSON_DESCRIPTION,
358 true ) );
359 waitForThreadToDie( t );
360
361 assertNotNull( listener.result );
362 assertEquals( RDN, listener.result.getName() );
363 assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
364
365 tearDownListener();
366 }
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487 class JndiNotificationListener implements NamespaceChangeListener, ObjectChangeListener
488 {
489 boolean hasError = false;
490 ArrayList<EventObject> list = new ArrayList<EventObject>();
491 NamingExceptionEvent exceptionEvent = null;
492
493 public void objectAdded( NamingEvent evt )
494 {
495 list.add( 0, evt );
496 }
497
498
499 public void objectRemoved( NamingEvent evt )
500 {
501 list.add( 0, evt );
502 }
503
504
505 public void objectRenamed( NamingEvent evt )
506 {
507 list.add( 0, evt );
508 }
509
510
511 public void namingExceptionThrown( NamingExceptionEvent evt )
512 {
513 hasError = true;
514 exceptionEvent = evt;
515 list.add( 0, evt );
516 }
517
518
519 public void objectChanged( NamingEvent evt )
520 {
521 list.add( 0, evt );
522 }
523 }
524
525
526 class PSearchListener implements Runnable
527 {
528 boolean isReady = false;
529 PSearchNotification result;
530 final PersistentSearchControl control;
531 LdapContext ctx;
532 NamingEnumeration<SearchResult> list;
533
534 PSearchListener()
535 {
536 control = new PersistentSearchControl();
537 }
538
539
540 PSearchListener(PersistentSearchControl control)
541 {
542 this.control = control;
543 }
544
545
546 void close()
547 {
548 if ( list != null )
549 {
550 try
551 {
552 list.close();
553 LOG.debug( "PSearchListener: search naming enumeration closed()" );
554 }
555 catch ( Exception e )
556 {
557 LOG.error( "Error closing NamingEnumeration on PSearchListener", e );
558 }
559 }
560
561 if ( ctx != null )
562 {
563 try
564 {
565 ctx.close();
566 LOG.debug( "PSearchListener: search context closed()" );
567 }
568 catch ( Exception e )
569 {
570 LOG.error( "Error closing connection on PSearchListener", e );
571 }
572 }
573 }
574
575
576 public void run()
577 {
578 LOG.debug( "PSearchListener.run() called." );
579 control.setCritical( true );
580 Control[] ctxCtls = new Control[]
581 { control };
582
583 try
584 {
585 ctx = ( LdapContext ) getWiredContext( ldapService).lookup( BASE );
586 ctx.setRequestControls( ctxCtls );
587 isReady = true;
588 LOG.debug( "PSearchListener is ready and about to issue persistent search request." );
589 list = ctx.search( "", "objectClass=*", null );
590 LOG.debug( "PSearchListener search request returned." );
591 EntryChangeControlCodec ecControl = null;
592
593 while ( list.hasMore() )
594 {
595 LOG.debug( "PSearchListener search request got an item." );
596 Control[] controls = null;
597 SearchResult sresult = list.next();
598 if ( sresult instanceof HasControls )
599 {
600 controls = ( ( HasControls ) sresult ).getControls();
601 if ( controls != null )
602 {
603 for ( int ii = 0; ii < controls.length; ii++ )
604 {
605 if ( controls[ii].getID().equals(
606 org.apache.directory.shared.ldap.message.EntryChangeControl.CONTROL_OID ) )
607 {
608 EntryChangeControlDecoder decoder = new EntryChangeControlDecoder();
609 ecControl = ( EntryChangeControlCodec ) decoder.decode( controls[ii].getEncodedValue() );
610 }
611 }
612 }
613 }
614 result = new PSearchNotification( sresult, ecControl );
615 break;
616 }
617 LOG.debug( "PSearchListener broke out of while loop." );
618 }
619 catch ( Exception e )
620 {
621 LOG.error( "PSearchListener encountered error", e );
622 }
623 finally
624 {
625 }
626 }
627 }
628
629
630 class PSearchNotification extends SearchResult
631 {
632 private static final long serialVersionUID = 1L;
633 final EntryChangeControlCodec control;
634
635
636 public PSearchNotification(SearchResult result, EntryChangeControlCodec control)
637 {
638 super( result.getName(), result.getClassName(), result.getObject(), result.getAttributes(), result
639 .isRelative() );
640 this.control = control;
641 }
642
643
644 public String toString()
645 {
646 StringBuffer buf = new StringBuffer();
647 buf.append( "DN: " ).append( getName() ).append( "\n" );
648 if ( control != null )
649 {
650 buf.append( " EntryChangeControl =\n" );
651 buf.append( " changeType : " ).append( control.getChangeType() ).append( "\n" );
652 buf.append( " previousDN : " ).append( control.getPreviousDn() ).append( "\n" );
653 buf.append( " changeNumber : " ).append( control.getChangeNumber() ).append( "\n" );
654 }
655 return buf.toString();
656 }
657 }
658 }