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