001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.core;
028
029
030 import java.util.ArrayList;
031 import java.util.Set;
032
033 import org.opends.server.controls.EntryChangeNotificationControl;
034 import org.opends.server.controls.PersistentSearchChangeType;
035 import org.opends.server.types.Control;
036 import org.opends.server.types.DN;
037 import org.opends.server.types.DebugLogLevel;
038 import org.opends.server.types.DirectoryException;
039 import org.opends.server.types.Entry;
040 import org.opends.server.types.SearchFilter;
041 import org.opends.server.types.SearchScope;
042 import org.opends.server.workflowelement.localbackend.*;
043
044 import static org.opends.server.loggers.debug.DebugLogger.*;
045 import org.opends.server.loggers.debug.DebugTracer;
046
047
048
049 /**
050 * This class defines a data structure that will be used to hold the information
051 * necessary for processing a persistent search.
052 */
053 public class PersistentSearch
054 {
055 /**
056 * The tracer object for the debug logger.
057 */
058 private static final DebugTracer TRACER = getTracer();
059
060 // Indicates whether entries returned should include the entry change
061 // notification control.
062 private boolean returnECs;
063
064 // The base DN for the search operation.
065 private DN baseDN;
066
067 // The set of change types we want to see.
068 private Set<PersistentSearchChangeType> changeTypes;
069
070 // The scope for the search operation.
071 private SearchScope scope;
072
073 // The filter for the search operation.
074 private SearchFilter filter;
075
076 // The reference to the associated search operation.
077 private SearchOperation searchOperation;
078
079
080
081 /**
082 * Creates a new persistent search object with the provided information.
083 *
084 * @param searchOperation The search operation for this persistent search.
085 * @param changeTypes The change types for which changes should be
086 * examined.
087 * @param returnECs Indicates whether to include entry change
088 * notification controls in search result entries
089 * sent to the client.
090 */
091 public PersistentSearch(SearchOperation searchOperation,
092 Set<PersistentSearchChangeType> changeTypes,
093 boolean returnECs)
094 {
095 this.searchOperation = searchOperation;
096 this.changeTypes = changeTypes;
097 this.returnECs = returnECs;
098
099 baseDN = searchOperation.getBaseDN();
100 scope = searchOperation.getScope();
101 filter = searchOperation.getFilter();
102 }
103
104
105
106 /**
107 * Retrieves the search operation for this persistent search.
108 *
109 * @return The search operation for this persistent search.
110 */
111 public SearchOperation getSearchOperation()
112 {
113 return searchOperation;
114 }
115
116
117
118 /**
119 * Retrieves the set of change types for this persistent search.
120 *
121 * @return The set of change types for this persistent search.
122 */
123 public Set<PersistentSearchChangeType> getChangeTypes()
124 {
125 return changeTypes;
126 }
127
128
129
130 /**
131 * Retrieves the returnECs flag for this persistent search.
132 *
133 * @return The return ECs flag for this persistent search.
134 */
135 public boolean getReturnECs()
136 {
137 return returnECs;
138 }
139
140
141
142 /**
143 * Retrieves the base DN for this persistent search.
144 *
145 * @return The base DN for this persistent search.
146 */
147 public DN getBaseDN()
148 {
149 return baseDN;
150 }
151
152
153
154 /**
155 * Retrieves the scope for this persistent search.
156 *
157 * @return The scope for this persistent search.
158 */
159 public SearchScope getScope()
160 {
161 return scope;
162 }
163
164
165
166 /**
167 * Retrieves the filter for this persistent search.
168 *
169 * @return The filter for this persistent search.
170 */
171 public SearchFilter getFilter()
172 {
173 return filter;
174 }
175
176
177
178 /**
179 * Performs any necessary processing for the provided add operation.
180 *
181 * @param addOperation The add operation that has been processed.
182 * @param entry The entry that was added.
183 */
184 public void processAdd(LocalBackendAddOperation addOperation, Entry entry)
185 {
186 // See if we care about add operations.
187 if (! changeTypes.contains(PersistentSearchChangeType.ADD))
188 {
189 return;
190 }
191
192
193 // Make sure that the entry is within our target scope.
194 switch (scope)
195 {
196 case BASE_OBJECT:
197 if (! baseDN.equals(entry.getDN()))
198 {
199 return;
200 }
201 break;
202 case SINGLE_LEVEL:
203 if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
204 {
205 return;
206 }
207 break;
208 case WHOLE_SUBTREE:
209 if (! baseDN.isAncestorOf(entry.getDN()))
210 {
211 return;
212 }
213 break;
214 case SUBORDINATE_SUBTREE:
215 if (baseDN.equals(entry.getDN()) ||
216 (! baseDN.isAncestorOf(entry.getDN())))
217 {
218 return;
219 }
220 break;
221 default:
222 return;
223 }
224
225
226 // Make sure that the entry matches the target filter.
227 try
228 {
229 if (! filter.matchesEntry(entry))
230 {
231 return;
232 }
233 }
234 catch (DirectoryException de)
235 {
236 if (debugEnabled())
237 {
238 TRACER.debugCaught(DebugLogLevel.ERROR, de);
239 }
240
241 // FIXME -- Do we need to do anything here?
242
243 return;
244 }
245
246
247 // The entry is one that should be sent to the client. See if we also need
248 // to construct an entry change notification control.
249 ArrayList<Control> entryControls = new ArrayList<Control>(1);
250 if (returnECs)
251 {
252 entryControls.add(new EntryChangeNotificationControl(
253 PersistentSearchChangeType.ADD,
254 addOperation.getChangeNumber()));
255 }
256
257
258 // Send the entry and see if we should continue processing. If not, then
259 // deregister this persistent search.
260 try
261 {
262 if (! searchOperation.returnEntry(entry, entryControls))
263 {
264 DirectoryServer.deregisterPersistentSearch(this);
265 searchOperation.sendSearchResultDone();
266 }
267 }
268 catch (Exception e)
269 {
270 if (debugEnabled())
271 {
272 TRACER.debugCaught(DebugLogLevel.ERROR, e);
273 }
274
275 DirectoryServer.deregisterPersistentSearch(this);
276
277 try
278 {
279 searchOperation.sendSearchResultDone();
280 }
281 catch (Exception e2)
282 {
283 if (debugEnabled())
284 {
285 TRACER.debugCaught(DebugLogLevel.ERROR, e2);
286 }
287 }
288 }
289 }
290
291
292
293 /**
294 * Performs any necessary processing for the provided delete operation.
295 *
296 * @param deleteOperation The delete operation that has been processed.
297 * @param entry The entry that was removed.
298 */
299 public void processDelete(LocalBackendDeleteOperation deleteOperation,
300 Entry entry)
301 {
302 // See if we care about delete operations.
303 if (! changeTypes.contains(PersistentSearchChangeType.DELETE))
304 {
305 return;
306 }
307
308
309 // Make sure that the entry is within our target scope.
310 switch (scope)
311 {
312 case BASE_OBJECT:
313 if (! baseDN.equals(entry.getDN()))
314 {
315 return;
316 }
317 break;
318 case SINGLE_LEVEL:
319 if (! baseDN.equals(entry.getDN().getParentDNInSuffix()))
320 {
321 return;
322 }
323 break;
324 case WHOLE_SUBTREE:
325 if (! baseDN.isAncestorOf(entry.getDN()))
326 {
327 return;
328 }
329 break;
330 case SUBORDINATE_SUBTREE:
331 if (baseDN.equals(entry.getDN()) ||
332 (! baseDN.isAncestorOf(entry.getDN())))
333 {
334 return;
335 }
336 break;
337 default:
338 return;
339 }
340
341
342 // Make sure that the entry matches the target filter.
343 try
344 {
345 if (! filter.matchesEntry(entry))
346 {
347 return;
348 }
349 }
350 catch (DirectoryException de)
351 {
352 if (debugEnabled())
353 {
354 TRACER.debugCaught(DebugLogLevel.ERROR, de);
355 }
356
357 // FIXME -- Do we need to do anything here?
358
359 return;
360 }
361
362
363 // The entry is one that should be sent to the client. See if we also need
364 // to construct an entry change notification control.
365 ArrayList<Control> entryControls = new ArrayList<Control>(1);
366 if (returnECs)
367 {
368 entryControls.add(new EntryChangeNotificationControl(
369 PersistentSearchChangeType.DELETE,
370 deleteOperation.getChangeNumber()));
371 }
372
373
374 // Send the entry and see if we should continue processing. If not, then
375 // deregister this persistent search.
376 try
377 {
378 if (! searchOperation.returnEntry(entry, entryControls))
379 {
380 DirectoryServer.deregisterPersistentSearch(this);
381 searchOperation.sendSearchResultDone();
382 }
383 }
384 catch (Exception e)
385 {
386 if (debugEnabled())
387 {
388 TRACER.debugCaught(DebugLogLevel.ERROR, e);
389 }
390
391 DirectoryServer.deregisterPersistentSearch(this);
392
393 try
394 {
395 searchOperation.sendSearchResultDone();
396 }
397 catch (Exception e2)
398 {
399 if (debugEnabled())
400 {
401 TRACER.debugCaught(DebugLogLevel.ERROR, e2);
402 }
403 }
404 }
405 }
406
407
408
409 /**
410 * Performs any necessary processing for the provided modify operation.
411 *
412 * @param modifyOperation The modify operation that has been processed.
413 * @param oldEntry The entry before the modification was applied.
414 * @param newEntry The entry after the modification was applied.
415 */
416 public void processModify(LocalBackendModifyOperation modifyOperation,
417 Entry oldEntry,
418 Entry newEntry)
419 {
420 // See if we care about modify operations.
421 if (! changeTypes.contains(PersistentSearchChangeType.MODIFY))
422 {
423 return;
424 }
425
426
427 // Make sure that the entry is within our target scope.
428 switch (scope)
429 {
430 case BASE_OBJECT:
431 if (! baseDN.equals(oldEntry.getDN()))
432 {
433 return;
434 }
435 break;
436 case SINGLE_LEVEL:
437 if (! baseDN.equals(oldEntry.getDN().getParentDNInSuffix()))
438 {
439 return;
440 }
441 break;
442 case WHOLE_SUBTREE:
443 if (! baseDN.isAncestorOf(oldEntry.getDN()))
444 {
445 return;
446 }
447 break;
448 case SUBORDINATE_SUBTREE:
449 if (baseDN.equals(oldEntry.getDN()) ||
450 (! baseDN.isAncestorOf(oldEntry.getDN())))
451 {
452 return;
453 }
454 break;
455 default:
456 return;
457 }
458
459
460 // Make sure that the entry matches the target filter.
461 try
462 {
463 if ((! filter.matchesEntry(oldEntry)) &&
464 (! filter.matchesEntry(newEntry)))
465 {
466 return;
467 }
468 }
469 catch (DirectoryException de)
470 {
471 if (debugEnabled())
472 {
473 TRACER.debugCaught(DebugLogLevel.ERROR, de);
474 }
475
476 // FIXME -- Do we need to do anything here?
477
478 return;
479 }
480
481
482 // The entry is one that should be sent to the client. See if we also need
483 // to construct an entry change notification control.
484 ArrayList<Control> entryControls = new ArrayList<Control>(1);
485 if (returnECs)
486 {
487 entryControls.add(new EntryChangeNotificationControl(
488 PersistentSearchChangeType.MODIFY,
489 modifyOperation.getChangeNumber()));
490 }
491
492
493 // Send the entry and see if we should continue processing. If not, then
494 // deregister this persistent search.
495 try
496 {
497 if (! searchOperation.returnEntry(newEntry, entryControls))
498 {
499 DirectoryServer.deregisterPersistentSearch(this);
500 searchOperation.sendSearchResultDone();
501 }
502 }
503 catch (Exception e)
504 {
505 if (debugEnabled())
506 {
507 TRACER.debugCaught(DebugLogLevel.ERROR, e);
508 }
509
510 DirectoryServer.deregisterPersistentSearch(this);
511
512 try
513 {
514 searchOperation.sendSearchResultDone();
515 }
516 catch (Exception e2)
517 {
518 if (debugEnabled())
519 {
520 TRACER.debugCaught(DebugLogLevel.ERROR, e2);
521 }
522 }
523 }
524 }
525
526
527
528 /**
529 * Performs any necessary processing for the provided modify DN operation.
530 *
531 * @param modifyDNOperation The modify DN operation that has been processed.
532 * @param oldEntry The entry before the modify DN.
533 * @param newEntry The entry after the modify DN.
534 */
535 public void processModifyDN(LocalBackendModifyDNOperation modifyDNOperation,
536 Entry oldEntry, Entry newEntry)
537 {
538 // See if we care about modify DN operations.
539 if (! changeTypes.contains(PersistentSearchChangeType.MODIFY_DN))
540 {
541 return;
542 }
543
544
545 // Make sure that the old or new entry is within our target scope. In this
546 // case, we need to check the DNs of both the old and new entry so we know
547 // which one(s) should be compared against the filter.
548 boolean oldMatches = false;
549 boolean newMatches = false;
550
551 switch (scope)
552 {
553 case BASE_OBJECT:
554 oldMatches = baseDN.equals(oldEntry.getDN());
555 newMatches = baseDN.equals(newEntry.getDN());
556
557 if (! (oldMatches || newMatches))
558 {
559 return;
560 }
561
562 break;
563 case SINGLE_LEVEL:
564 oldMatches = baseDN.equals(oldEntry.getDN().getParentDNInSuffix());
565 newMatches = baseDN.equals(newEntry.getDN().getParentDNInSuffix());
566
567 if (! (oldMatches || newMatches))
568 {
569 return;
570 }
571
572 break;
573 case WHOLE_SUBTREE:
574 oldMatches = baseDN.isAncestorOf(oldEntry.getDN());
575 newMatches = baseDN.isAncestorOf(newEntry.getDN());
576
577 if (! (oldMatches || newMatches))
578 {
579 return;
580 }
581
582 break;
583 case SUBORDINATE_SUBTREE:
584 oldMatches = ((! baseDN.equals(oldEntry.getDN())) &&
585 baseDN.isAncestorOf(oldEntry.getDN()));
586 newMatches = ((! baseDN.equals(newEntry.getDN())) &&
587 baseDN.isAncestorOf(newEntry.getDN()));
588
589 if (! (oldMatches || newMatches))
590 {
591 return;
592 }
593
594 break;
595 default:
596 return;
597 }
598
599
600 // Make sure that the entry matches the target filter.
601 try
602 {
603 if (((! oldMatches) || (! filter.matchesEntry(oldEntry))) &&
604 ((! newMatches) && (! filter.matchesEntry(newEntry))))
605 {
606 return;
607 }
608 }
609 catch (DirectoryException de)
610 {
611 if (debugEnabled())
612 {
613 TRACER.debugCaught(DebugLogLevel.ERROR, de);
614 }
615
616 // FIXME -- Do we need to do anything here?
617
618 return;
619 }
620
621
622 // The entry is one that should be sent to the client. See if we also need
623 // to construct an entry change notification control.
624 ArrayList<Control> entryControls = new ArrayList<Control>(1);
625 if (returnECs)
626 {
627 entryControls.add(new EntryChangeNotificationControl(
628 PersistentSearchChangeType.MODIFY_DN,
629 oldEntry.getDN(),
630 modifyDNOperation.getChangeNumber()));
631 }
632
633
634 // Send the entry and see if we should continue processing. If not, then
635 // deregister this persistent search.
636 try
637 {
638 if (! searchOperation.returnEntry(newEntry, entryControls))
639 {
640 DirectoryServer.deregisterPersistentSearch(this);
641 searchOperation.sendSearchResultDone();
642 }
643 }
644 catch (Exception e)
645 {
646 if (debugEnabled())
647 {
648 TRACER.debugCaught(DebugLogLevel.ERROR, e);
649 }
650
651 DirectoryServer.deregisterPersistentSearch(this);
652
653 try
654 {
655 searchOperation.sendSearchResultDone();
656 }
657 catch (Exception e2)
658 {
659 if (debugEnabled())
660 {
661 TRACER.debugCaught(DebugLogLevel.ERROR, e2);
662 }
663 }
664 }
665 }
666
667
668
669 /**
670 * Retrieves a string representation of this persistent search.
671 *
672 * @return A string representation of this persistent search.
673 */
674 public String toString()
675 {
676 StringBuilder buffer = new StringBuilder();
677 toString(buffer);
678 return buffer.toString();
679 }
680
681
682
683 /**
684 * Appends a string representation of this persistent search to the provided
685 * buffer.
686 *
687 * @param buffer The buffer to which the information should be appended.
688 */
689 public void toString(StringBuilder buffer)
690 {
691 buffer.append("PersistentSearch(connID=");
692 buffer.append(searchOperation.getConnectionID());
693 buffer.append(",opID=");
694 buffer.append(searchOperation.getOperationID());
695 buffer.append(",baseDN=\"");
696 searchOperation.getBaseDN().toString(buffer);
697 buffer.append("\",scope=");
698 buffer.append(scope.toString());
699 buffer.append(",filter=\"");
700 filter.toString(buffer);
701 buffer.append("\")");
702 }
703 }
704