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 import org.opends.messages.MessageBuilder; 030 import org.opends.messages.Message; 031 032 import java.util.ArrayList; 033 import java.util.HashSet; 034 import java.util.Iterator; 035 import java.util.LinkedHashSet; 036 import java.util.List; 037 import java.util.concurrent.atomic.AtomicBoolean; 038 import org.opends.server.api.ClientConnection; 039 import org.opends.server.api.plugin.PluginResult; 040 import org.opends.server.controls.AccountUsableResponseControl; 041 import org.opends.server.controls.MatchedValuesControl; 042 import org.opends.server.loggers.debug.DebugLogger; 043 import org.opends.server.loggers.debug.DebugTracer; 044 import org.opends.server.protocols.asn1.ASN1OctetString; 045 import org.opends.server.protocols.ldap.LDAPFilter; 046 import org.opends.server.types.*; 047 import org.opends.server.types.operation.PostResponseSearchOperation; 048 import org.opends.server.types.operation.PreParseSearchOperation; 049 import org.opends.server.types.operation.SearchEntrySearchOperation; 050 import org.opends.server.types.operation.SearchReferenceSearchOperation; 051 import org.opends.server.util.TimeThread; 052 053 import static org.opends.server.core.CoreConstants.*; 054 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; 055 import static org.opends.server.loggers.AccessLogger.*; 056 import static org.opends.messages.CoreMessages.*; 057 import static org.opends.server.util.StaticUtils.toLowerCase; 058 059 /** 060 * This class defines an operation that may be used to locate entries in the 061 * Directory Server based on a given set of criteria. 062 */ 063 public class SearchOperationBasis 064 extends AbstractOperation 065 implements PreParseSearchOperation, 066 PostResponseSearchOperation, 067 SearchEntrySearchOperation, 068 SearchReferenceSearchOperation, 069 SearchOperation 070 { 071 /** 072 * The tracer object for the debug logger. 073 */ 074 private static final DebugTracer TRACER = DebugLogger.getTracer(); 075 076 // Indicates whether a search result done response has been sent to the 077 // client. 078 private AtomicBoolean responseSent; 079 080 // Indicates whether the client is able to handle referrals. 081 private boolean clientAcceptsReferrals; 082 083 // Indicates whether to include the account usable control with search result 084 // entries. 085 private boolean includeUsableControl; 086 087 // Indicates whether to only real attributes should be returned. 088 private boolean realAttributesOnly; 089 090 // Indicates whether LDAP subentries should be returned. 091 private boolean returnLDAPSubentries; 092 093 // Indicates whether to include attribute types only or both types and values. 094 private boolean typesOnly; 095 096 // Indicates whether to only virtual attributes should be returned. 097 private boolean virtualAttributesOnly; 098 099 // The raw, unprocessed base DN as included in the request from the client. 100 private ByteString rawBaseDN; 101 102 // The dereferencing policy for the search operation. 103 private DereferencePolicy derefPolicy; 104 105 // The base DN for the search operation. 106 private DN baseDN; 107 108 // The proxied authorization target DN for this operation. 109 private DN proxiedAuthorizationDN; 110 111 // The number of entries that have been sent to the client. 112 private int entriesSent; 113 114 // The number of search result references that have been sent to the client. 115 private int referencesSent; 116 117 // The size limit for the search operation. 118 private int sizeLimit; 119 120 // The time limit for the search operation. 121 private int timeLimit; 122 123 // The raw, unprocessed filter as included in the request from the client. 124 private RawFilter rawFilter; 125 126 // The set of attributes that should be returned in matching entries. 127 private LinkedHashSet<String> attributes; 128 129 // The set of response controls for this search operation. 130 private List<Control> responseControls; 131 132 // The time that processing started on this operation. 133 private long processingStartTime; 134 135 // The time that processing ended on this operation. 136 private long processingStopTime; 137 138 // The time that the search time limit has expired. 139 private long timeLimitExpiration; 140 141 // The matched values control associated with this search operation. 142 private MatchedValuesControl matchedValuesControl; 143 144 // The persistent search associated with this search operation. 145 private PersistentSearch persistentSearch; 146 147 // The search filter for the search operation. 148 private SearchFilter filter; 149 150 // The search scope for the search operation. 151 private SearchScope scope; 152 153 // Indicates wether to send the search result done to the client or not 154 private boolean sendResponse = true; 155 156 /** 157 * Creates a new search operation with the provided information. 158 * 159 * @param clientConnection The client connection with which this operation 160 * is associated. 161 * @param operationID The operation ID for this operation. 162 * @param messageID The message ID of the request with which this 163 * operation is associated. 164 * @param requestControls The set of controls included in the request. 165 * @param rawBaseDN The raw, unprocessed base DN as included in the 166 * request from the client. 167 * @param scope The scope for this search operation. 168 * @param derefPolicy The alias dereferencing policy for this search 169 * operation. 170 * @param sizeLimit The size limit for this search operation. 171 * @param timeLimit The time limit for this search operation. 172 * @param typesOnly The typesOnly flag for this search operation. 173 * @param rawFilter the raw, unprocessed filter as included in the 174 * request from the client. 175 * @param attributes The requested attributes for this search 176 * operation. 177 */ 178 public SearchOperationBasis(ClientConnection clientConnection, 179 long operationID, 180 int messageID, List<Control> requestControls, 181 ByteString rawBaseDN, SearchScope scope, 182 DereferencePolicy derefPolicy, int sizeLimit, 183 int timeLimit, boolean typesOnly, RawFilter rawFilter, 184 LinkedHashSet<String> attributes) 185 { 186 super(clientConnection, operationID, messageID, requestControls); 187 188 189 this.rawBaseDN = rawBaseDN; 190 this.scope = scope; 191 this.derefPolicy = derefPolicy; 192 this.sizeLimit = sizeLimit; 193 this.timeLimit = timeLimit; 194 this.typesOnly = typesOnly; 195 this.rawFilter = rawFilter; 196 197 if (attributes == null) 198 { 199 this.attributes = new LinkedHashSet<String>(0); 200 } 201 else 202 { 203 this.attributes = attributes; 204 } 205 206 207 if (clientConnection.getSizeLimit() <= 0) 208 { 209 this.sizeLimit = sizeLimit; 210 } 211 else 212 { 213 if (sizeLimit <= 0) 214 { 215 this.sizeLimit = clientConnection.getSizeLimit(); 216 } 217 else 218 { 219 this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit()); 220 } 221 } 222 223 224 if (clientConnection.getTimeLimit() <= 0) 225 { 226 this.timeLimit = timeLimit; 227 } 228 else 229 { 230 if (timeLimit <= 0) 231 { 232 this.timeLimit = clientConnection.getTimeLimit(); 233 } 234 else 235 { 236 this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit()); 237 } 238 } 239 240 241 baseDN = null; 242 filter = null; 243 entriesSent = 0; 244 referencesSent = 0; 245 responseControls = new ArrayList<Control>(); 246 cancelRequest = null; 247 clientAcceptsReferrals = true; 248 includeUsableControl = false; 249 responseSent = new AtomicBoolean(false); 250 persistentSearch = null; 251 returnLDAPSubentries = false; 252 matchedValuesControl = null; 253 realAttributesOnly = false; 254 virtualAttributesOnly = false; 255 } 256 257 258 259 /** 260 * Creates a new search operation with the provided information. 261 * 262 * @param clientConnection The client connection with which this operation 263 * is associated. 264 * @param operationID The operation ID for this operation. 265 * @param messageID The message ID of the request with which this 266 * operation is associated. 267 * @param requestControls The set of controls included in the request. 268 * @param baseDN The base DN for this search operation. 269 * @param scope The scope for this search operation. 270 * @param derefPolicy The alias dereferencing policy for this search 271 * operation. 272 * @param sizeLimit The size limit for this search operation. 273 * @param timeLimit The time limit for this search operation. 274 * @param typesOnly The typesOnly flag for this search operation. 275 * @param filter The filter for this search operation. 276 * @param attributes The attributes for this search operation. 277 */ 278 public SearchOperationBasis(ClientConnection clientConnection, 279 long operationID, 280 int messageID, List<Control> requestControls, 281 DN baseDN, SearchScope scope, 282 DereferencePolicy derefPolicy, int sizeLimit, 283 int timeLimit, boolean typesOnly, SearchFilter filter, 284 LinkedHashSet<String> attributes) 285 { 286 super(clientConnection, operationID, messageID, requestControls); 287 288 289 this.baseDN = baseDN; 290 this.scope = scope; 291 this.derefPolicy = derefPolicy; 292 this.sizeLimit = sizeLimit; 293 this.timeLimit = timeLimit; 294 this.typesOnly = typesOnly; 295 this.filter = filter; 296 297 if (attributes == null) 298 { 299 this.attributes = new LinkedHashSet<String>(0); 300 } 301 else 302 { 303 this.attributes = attributes; 304 } 305 306 rawBaseDN = new ASN1OctetString(baseDN.toString()); 307 rawFilter = new LDAPFilter(filter); 308 309 310 if (clientConnection.getSizeLimit() <= 0) 311 { 312 this.sizeLimit = sizeLimit; 313 } 314 else 315 { 316 if (sizeLimit <= 0) 317 { 318 this.sizeLimit = clientConnection.getSizeLimit(); 319 } 320 else 321 { 322 this.sizeLimit = Math.min(sizeLimit, clientConnection.getSizeLimit()); 323 } 324 } 325 326 327 if (clientConnection.getTimeLimit() <= 0) 328 { 329 this.timeLimit = timeLimit; 330 } 331 else 332 { 333 if (timeLimit <= 0) 334 { 335 this.timeLimit = clientConnection.getTimeLimit(); 336 } 337 else 338 { 339 this.timeLimit = Math.min(timeLimit, clientConnection.getTimeLimit()); 340 } 341 } 342 343 344 entriesSent = 0; 345 referencesSent = 0; 346 responseControls = new ArrayList<Control>(); 347 cancelRequest = null; 348 clientAcceptsReferrals = true; 349 includeUsableControl = false; 350 responseSent = new AtomicBoolean(false); 351 persistentSearch = null; 352 returnLDAPSubentries = false; 353 matchedValuesControl = null; 354 } 355 356 357 358 /** 359 * {@inheritDoc} 360 */ 361 public final ByteString getRawBaseDN() 362 { 363 return rawBaseDN; 364 } 365 366 367 368 /** 369 * {@inheritDoc} 370 */ 371 public final void setRawBaseDN(ByteString rawBaseDN) 372 { 373 this.rawBaseDN = rawBaseDN; 374 375 baseDN = null; 376 } 377 378 379 /** 380 * {@inheritDoc} 381 */ 382 public final DN getBaseDN() 383 { 384 try 385 { 386 if (baseDN == null) 387 { 388 baseDN = DN.decode(rawBaseDN); 389 } 390 } 391 catch (DirectoryException de) 392 { 393 if (debugEnabled()) 394 { 395 TRACER.debugCaught(DebugLogLevel.ERROR, de); 396 } 397 398 setResultCode(de.getResultCode()); 399 appendErrorMessage(de.getMessageObject()); 400 setMatchedDN(de.getMatchedDN()); 401 setReferralURLs(de.getReferralURLs()); 402 } 403 return baseDN; 404 } 405 406 407 /** 408 * {@inheritDoc} 409 */ 410 public final void setBaseDN(DN baseDN) 411 { 412 this.baseDN = baseDN; 413 } 414 415 /** 416 * {@inheritDoc} 417 */ 418 public final SearchScope getScope() 419 { 420 return scope; 421 } 422 423 /** 424 * {@inheritDoc} 425 */ 426 public final void setScope(SearchScope scope) 427 { 428 this.scope = scope; 429 } 430 431 /** 432 * {@inheritDoc} 433 */ 434 public final DereferencePolicy getDerefPolicy() 435 { 436 return derefPolicy; 437 } 438 439 /** 440 * {@inheritDoc} 441 */ 442 public final void setDerefPolicy(DereferencePolicy derefPolicy) 443 { 444 this.derefPolicy = derefPolicy; 445 } 446 447 /** 448 * {@inheritDoc} 449 */ 450 public final int getSizeLimit() 451 { 452 return sizeLimit; 453 } 454 455 /** 456 * {@inheritDoc} 457 */ 458 public final void setSizeLimit(int sizeLimit) 459 { 460 this.sizeLimit = sizeLimit; 461 } 462 463 /** 464 * {@inheritDoc} 465 */ 466 public final int getTimeLimit() 467 { 468 return timeLimit; 469 } 470 471 /** 472 * {@inheritDoc} 473 */ 474 public final void setTimeLimit(int timeLimit) 475 { 476 this.timeLimit = timeLimit; 477 } 478 479 /** 480 * {@inheritDoc} 481 */ 482 public final boolean getTypesOnly() 483 { 484 return typesOnly; 485 } 486 487 /** 488 * {@inheritDoc} 489 */ 490 public final void setTypesOnly(boolean typesOnly) 491 { 492 this.typesOnly = typesOnly; 493 } 494 495 /** 496 * {@inheritDoc} 497 */ 498 public final RawFilter getRawFilter() 499 { 500 return rawFilter; 501 } 502 503 /** 504 * {@inheritDoc} 505 */ 506 public final void setRawFilter(RawFilter rawFilter) 507 { 508 this.rawFilter = rawFilter; 509 510 filter = null; 511 } 512 513 /** 514 * {@inheritDoc} 515 */ 516 public final SearchFilter getFilter() 517 { 518 try 519 { 520 if (filter == null) 521 { 522 filter = rawFilter.toSearchFilter(); 523 } 524 } 525 catch (DirectoryException de) 526 { 527 if (debugEnabled()) 528 { 529 TRACER.debugCaught(DebugLogLevel.ERROR, de); 530 } 531 532 setResultCode(de.getResultCode()); 533 appendErrorMessage(de.getMessageObject()); 534 setMatchedDN(de.getMatchedDN()); 535 setReferralURLs(de.getReferralURLs()); 536 } 537 return filter; 538 } 539 540 /** 541 * {@inheritDoc} 542 */ 543 public final LinkedHashSet<String> getAttributes() 544 { 545 return attributes; 546 } 547 548 /** 549 * {@inheritDoc} 550 */ 551 public final void setAttributes(LinkedHashSet<String> attributes) 552 { 553 if (attributes == null) 554 { 555 this.attributes.clear(); 556 } 557 else 558 { 559 this.attributes = attributes; 560 } 561 } 562 563 /** 564 * {@inheritDoc} 565 */ 566 public final int getEntriesSent() 567 { 568 return entriesSent; 569 } 570 571 /** 572 * {@inheritDoc} 573 */ 574 public final int getReferencesSent() 575 { 576 return referencesSent; 577 } 578 579 /** 580 * {@inheritDoc} 581 */ 582 public final boolean returnEntry(Entry entry, List<Control> controls) 583 { 584 boolean typesOnly = getTypesOnly(); 585 // See if the operation has been abandoned. If so, then don't send the 586 // entry and indicate that the search should end. 587 if (getCancelRequest() != null) 588 { 589 setResultCode(ResultCode.CANCELED); 590 return false; 591 } 592 593 // See if the size limit has been exceeded. If so, then don't send the 594 // entry and indicate that the search should end. 595 if ((getSizeLimit() > 0) && (getEntriesSent() >= getSizeLimit())) 596 { 597 setResultCode(ResultCode.SIZE_LIMIT_EXCEEDED); 598 appendErrorMessage(ERR_SEARCH_SIZE_LIMIT_EXCEEDED.get(getSizeLimit())); 599 return false; 600 } 601 602 // See if the time limit has expired. If so, then don't send the entry and 603 // indicate that the search should end. 604 if ((getTimeLimit() > 0) && (TimeThread.getTime() >= 605 getTimeLimitExpiration())) 606 { 607 setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); 608 appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit())); 609 return false; 610 } 611 612 // Determine whether the provided entry is a subentry and if so whether it 613 // should be returned. 614 if ((getScope() != SearchScope.BASE_OBJECT) && 615 (! isReturnLDAPSubentries()) && 616 entry.isLDAPSubentry()) 617 { 618 // Check to see if the filter contains an equality element with the 619 // objectclass attribute type and a value of "ldapSubentry". If so, then 620 // we'll return it anyway. Technically, this isn't part of the 621 // specification so we don't need to get carried away with really in-depth 622 // checks. 623 SearchFilter filter = getFilter(); 624 switch (filter.getFilterType()) 625 { 626 case AND: 627 case OR: 628 for (SearchFilter f : filter.getFilterComponents()) 629 { 630 if ((f.getFilterType() == FilterType.EQUALITY) && 631 (f.getAttributeType().isObjectClassType())) 632 { 633 AttributeValue v = f.getAssertionValue(); 634 if (toLowerCase(v.getStringValue()).equals("ldapsubentry")) 635 { 636 setReturnLDAPSubentries(true); 637 } 638 break; 639 } 640 } 641 break; 642 case EQUALITY: 643 AttributeType t = filter.getAttributeType(); 644 if (t.isObjectClassType()) 645 { 646 AttributeValue v = filter.getAssertionValue(); 647 if (toLowerCase(v.getStringValue()).equals("ldapsubentry")) 648 { 649 setReturnLDAPSubentries(true); 650 } 651 } 652 break; 653 } 654 655 if (! isReturnLDAPSubentries()) 656 { 657 // We still shouldn't return it even based on the filter. Just throw it 658 // away without doing anything. 659 return true; 660 } 661 } 662 663 664 // Determine whether to include the account usable control. If so, then 665 // create it now. 666 if (isIncludeUsableControl()) 667 { 668 try 669 { 670 // FIXME -- Need a way to enable PWP debugging. 671 PasswordPolicyState pwpState = new PasswordPolicyState(entry, false); 672 673 boolean isInactive = pwpState.isDisabled() || 674 pwpState.isAccountExpired(); 675 boolean isLocked = pwpState.lockedDueToFailures() || 676 pwpState.lockedDueToMaximumResetAge() || 677 pwpState.lockedDueToIdleInterval(); 678 boolean isReset = pwpState.mustChangePassword(); 679 boolean isExpired = pwpState.isPasswordExpired(); 680 681 if (isInactive || isLocked || isReset || isExpired) 682 { 683 int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock(); 684 int remainingGraceLogins = pwpState.getGraceLoginsRemaining(); 685 686 if (controls == null) 687 { 688 controls = new ArrayList<Control>(1); 689 } 690 691 controls.add(new AccountUsableResponseControl(isInactive, isReset, 692 isExpired, remainingGraceLogins, isLocked, 693 secondsBeforeUnlock)); 694 } 695 else 696 { 697 if (controls == null) 698 { 699 controls = new ArrayList<Control>(1); 700 } 701 702 int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration(); 703 controls.add(new AccountUsableResponseControl( 704 secondsBeforeExpiration)); 705 } 706 } 707 catch (Exception e) 708 { 709 if (debugEnabled()) 710 { 711 TRACER.debugCaught(DebugLogLevel.ERROR, e); 712 } 713 } 714 } 715 716 // Check to see if the entry can be read by the client. 717 SearchResultEntry tmpSearchEntry = new SearchResultEntry(entry, 718 controls); 719 if (AccessControlConfigManager.getInstance() 720 .getAccessControlHandler().maySend(this, tmpSearchEntry) == false) { 721 return true; 722 } 723 724 // Make a copy of the entry and pare it down to only include the set 725 // of 726 // requested attributes. 727 Entry entryToReturn; 728 if ((getAttributes() == null) || getAttributes().isEmpty()) 729 { 730 entryToReturn = entry.duplicateWithoutOperationalAttributes(typesOnly, 731 true); 732 } 733 else 734 { 735 entryToReturn = entry.duplicateWithoutAttributes(); 736 737 for (String attrName : getAttributes()) 738 { 739 if (attrName.equals("*")) 740 { 741 // This is a special placeholder indicating that all user attributes 742 // should be returned. 743 if (typesOnly) 744 { 745 // First, add the placeholder for the objectclass attribute. 746 AttributeType ocType = 747 DirectoryServer.getObjectClassAttributeType(); 748 List<Attribute> ocList = new ArrayList<Attribute>(1); 749 ocList.add(new Attribute(ocType)); 750 entryToReturn.putAttribute(ocType, ocList); 751 } 752 else 753 { 754 // First, add the objectclass attribute. 755 Attribute ocAttr = entry.getObjectClassAttribute(); 756 try 757 { 758 if (ocAttr != null) 759 entryToReturn.setObjectClasses(ocAttr.getValues()); 760 } 761 catch (DirectoryException e) 762 { 763 // We cannot get this exception because the object classes have 764 // already been validated in the entry they came from. 765 } 766 } 767 768 // Next iterate through all the user attributes and include them. 769 for (AttributeType t : entry.getUserAttributes().keySet()) 770 { 771 List<Attribute> attrList = 772 entry.duplicateUserAttribute(t, null, typesOnly); 773 entryToReturn.putAttribute(t, attrList); 774 } 775 776 continue; 777 } 778 else if (attrName.equals("+")) 779 { 780 // This is a special placeholder indicating that all operational 781 // attributes should be returned. 782 for (AttributeType t : entry.getOperationalAttributes().keySet()) 783 { 784 List<Attribute> attrList = 785 entry.duplicateOperationalAttribute(t, null, typesOnly); 786 entryToReturn.putAttribute(t, attrList); 787 } 788 789 continue; 790 } 791 792 String lowerName; 793 HashSet<String> options; 794 int semicolonPos = attrName.indexOf(';'); 795 if (semicolonPos > 0) 796 { 797 lowerName = toLowerCase(attrName.substring(0, semicolonPos)); 798 int nextPos = attrName.indexOf(';', semicolonPos+1); 799 options = new HashSet<String>(); 800 while (nextPos > 0) 801 { 802 options.add(attrName.substring(semicolonPos+1, nextPos)); 803 804 semicolonPos = nextPos; 805 nextPos = attrName.indexOf(';', semicolonPos+1); 806 } 807 808 options.add(attrName.substring(semicolonPos+1)); 809 } 810 else 811 { 812 lowerName = toLowerCase(attrName); 813 options = null; 814 } 815 816 817 AttributeType attrType = DirectoryServer.getAttributeType(lowerName); 818 if (attrType == null) 819 { 820 boolean added = false; 821 for (AttributeType t : entry.getUserAttributes().keySet()) 822 { 823 if (t.hasNameOrOID(lowerName)) 824 { 825 List<Attribute> attrList = 826 entry.duplicateUserAttribute(t, options, typesOnly); 827 if (attrList != null) 828 { 829 entryToReturn.putAttribute(t, attrList); 830 831 added = true; 832 break; 833 } 834 } 835 } 836 837 if (added) 838 { 839 continue; 840 } 841 842 for (AttributeType t : entry.getOperationalAttributes().keySet()) 843 { 844 if (t.hasNameOrOID(lowerName)) 845 { 846 List<Attribute> attrList = 847 entry.duplicateOperationalAttribute(t, options, typesOnly); 848 if (attrList != null) 849 { 850 entryToReturn.putAttribute(t, attrList); 851 852 break; 853 } 854 } 855 } 856 } 857 else 858 { 859 if (attrType.isObjectClassType()) { 860 if (typesOnly) 861 { 862 AttributeType ocType = 863 DirectoryServer.getObjectClassAttributeType(); 864 List<Attribute> ocList = new ArrayList<Attribute>(1); 865 ocList.add(new Attribute(ocType)); 866 entryToReturn.putAttribute(ocType, ocList); 867 } 868 else 869 { 870 List<Attribute> attrList = new ArrayList<Attribute>(1); 871 attrList.add(entry.getObjectClassAttribute()); 872 entryToReturn.putAttribute(attrType, attrList); 873 } 874 } 875 else 876 { 877 List<Attribute> attrList = 878 entry.duplicateOperationalAttribute(attrType, options, 879 typesOnly); 880 if (attrList == null) 881 { 882 attrList = entry.duplicateUserAttribute(attrType, options, 883 typesOnly); 884 } 885 if (attrList != null) 886 { 887 entryToReturn.putAttribute(attrType, attrList); 888 } 889 } 890 } 891 } 892 } 893 894 if (isRealAttributesOnly()) 895 { 896 entryToReturn.stripVirtualAttributes(); 897 } 898 else if (isVirtualAttributesOnly()) 899 { 900 entryToReturn.stripRealAttributes(); 901 } 902 903 // If there is a matched values control, then further pare down the entry 904 // based on the filters that it contains. 905 MatchedValuesControl matchedValuesControl = getMatchedValuesControl(); 906 if ((matchedValuesControl != null) && (! typesOnly)) 907 { 908 // First, look at the set of objectclasses. 909 AttributeType attrType = DirectoryServer.getObjectClassAttributeType(); 910 Iterator<String> ocIterator = 911 entryToReturn.getObjectClasses().values().iterator(); 912 while (ocIterator.hasNext()) 913 { 914 String ocName = ocIterator.next(); 915 AttributeValue v = new AttributeValue(attrType, 916 new ASN1OctetString(ocName)); 917 if (! matchedValuesControl.valueMatches(attrType, v)) 918 { 919 ocIterator.remove(); 920 } 921 } 922 923 924 // Next, the set of user attributes. 925 for (AttributeType t : entryToReturn.getUserAttributes().keySet()) 926 { 927 for (Attribute a : entryToReturn.getUserAttribute(t)) 928 { 929 Iterator<AttributeValue> valueIterator = a.getValues().iterator(); 930 while (valueIterator.hasNext()) 931 { 932 AttributeValue v = valueIterator.next(); 933 if (! matchedValuesControl.valueMatches(t, v)) 934 { 935 valueIterator.remove(); 936 } 937 } 938 } 939 } 940 941 942 // Then the set of operational attributes. 943 for (AttributeType t : entryToReturn.getOperationalAttributes().keySet()) 944 { 945 for (Attribute a : entryToReturn.getOperationalAttribute(t)) 946 { 947 Iterator<AttributeValue> valueIterator = a.getValues().iterator(); 948 while (valueIterator.hasNext()) 949 { 950 AttributeValue v = valueIterator.next(); 951 if (! matchedValuesControl.valueMatches(t, v)) 952 { 953 valueIterator.remove(); 954 } 955 } 956 } 957 } 958 } 959 960 961 // Convert the provided entry to a search result entry. 962 SearchResultEntry searchEntry = new SearchResultEntry(entryToReturn, 963 controls); 964 965 // Strip out any attributes that the client does not have access to. 966 967 // FIXME: need some way to prevent plugins from adding attributes or 968 // values that the client is not permitted to see. 969 searchEntry = AccessControlConfigManager.getInstance() 970 .getAccessControlHandler().filterEntry(this, searchEntry); 971 972 // Invoke any search entry plugins that may be registered with the server. 973 PluginResult.IntermediateResponse pluginResult = 974 DirectoryServer.getPluginConfigManager(). 975 invokeSearchResultEntryPlugins(this, searchEntry); 976 977 // Send the entry to the client. 978 if (pluginResult.sendResponse()) 979 { 980 try 981 { 982 sendSearchEntry(searchEntry); 983 // Log the entry sent to the client. 984 logSearchResultEntry(this, searchEntry); 985 986 incrementEntriesSent(); 987 } 988 catch (DirectoryException de) 989 { 990 if (debugEnabled()) 991 { 992 TRACER.debugCaught(DebugLogLevel.ERROR, de); 993 } 994 995 setResponseData(de); 996 return false; 997 } 998 } 999 1000 return pluginResult.continueProcessing(); 1001 } 1002 1003 /** 1004 * {@inheritDoc} 1005 */ 1006 public final boolean returnReference(DN dn, SearchResultReference reference) 1007 { 1008 // See if the operation has been abandoned. If so, then don't send the 1009 // reference and indicate that the search should end. 1010 if (getCancelRequest() != null) 1011 { 1012 setResultCode(ResultCode.CANCELED); 1013 return false; 1014 } 1015 1016 1017 // See if the time limit has expired. If so, then don't send the entry and 1018 // indicate that the search should end. 1019 if ((getTimeLimit() > 0) && (TimeThread.getTime() >= 1020 getTimeLimitExpiration())) 1021 { 1022 setResultCode(ResultCode.TIME_LIMIT_EXCEEDED); 1023 appendErrorMessage(ERR_SEARCH_TIME_LIMIT_EXCEEDED.get(getTimeLimit())); 1024 return false; 1025 } 1026 1027 1028 // See if we know that this client can't handle referrals. If so, then 1029 // don't even try to send it. 1030 if (! isClientAcceptsReferrals()) 1031 { 1032 return true; 1033 } 1034 1035 1036 // See if the client has permission to read this reference. 1037 if (AccessControlConfigManager.getInstance() 1038 .getAccessControlHandler().maySend(dn, this, reference) == false) { 1039 return true; 1040 } 1041 1042 1043 // Invoke any search reference plugins that may be registered with the 1044 // server. 1045 PluginResult.IntermediateResponse pluginResult = 1046 DirectoryServer.getPluginConfigManager(). 1047 invokeSearchResultReferencePlugins(this, reference); 1048 1049 // Send the reference to the client. Note that this could throw an 1050 // exception, which would indicate that the associated client can't handle 1051 // referrals. If that't the case, then set a flag so we'll know not to try 1052 // to send any more. 1053 if (pluginResult.sendResponse()) 1054 { 1055 try 1056 { 1057 if (sendSearchReference(reference)) 1058 { 1059 // Log the entry sent to the client. 1060 logSearchResultReference(this, reference); 1061 incrementReferencesSent(); 1062 1063 // FIXME -- Should the size limit apply here? 1064 } 1065 else 1066 { 1067 // We know that the client can't handle referrals, so we won't try to 1068 // send it any more. 1069 setClientAcceptsReferrals(false); 1070 } 1071 } 1072 catch (DirectoryException de) 1073 { 1074 if (debugEnabled()) 1075 { 1076 TRACER.debugCaught(DebugLogLevel.ERROR, de); 1077 } 1078 1079 setResponseData(de); 1080 return false; 1081 } 1082 } 1083 1084 return pluginResult.continueProcessing(); 1085 } 1086 1087 /** 1088 * {@inheritDoc} 1089 */ 1090 public final void sendSearchResultDone() 1091 { 1092 // Send the search result done message to the client. We want to make sure 1093 // that this only gets sent once, and it's possible that this could be 1094 // multithreaded in the event of a persistent search, so do it safely. 1095 if (responseSent.compareAndSet(false, true)) 1096 { 1097 // Send the response to the client. 1098 clientConnection.sendResponse(this); 1099 1100 // Log the search result. 1101 logSearchResultDone(this); 1102 1103 1104 // Invoke the post-response search plugins. 1105 invokePostResponsePlugins(); 1106 } 1107 } 1108 1109 /** 1110 * {@inheritDoc} 1111 */ 1112 @Override() 1113 public final OperationType getOperationType() 1114 { 1115 // Note that no debugging will be done in this method because it is a likely 1116 // candidate for being called by the logging subsystem. 1117 1118 return OperationType.SEARCH; 1119 } 1120 1121 /** 1122 * {@inheritDoc} 1123 */ 1124 @Override() 1125 public final String[][] getRequestLogElements() 1126 { 1127 // Note that no debugging will be done in this method because it is a likely 1128 // candidate for being called by the logging subsystem. 1129 1130 String attrs; 1131 if ((attributes == null) || attributes.isEmpty()) 1132 { 1133 attrs = null; 1134 } 1135 else 1136 { 1137 StringBuilder attrBuffer = new StringBuilder(); 1138 Iterator<String> iterator = attributes.iterator(); 1139 attrBuffer.append(iterator.next()); 1140 1141 while (iterator.hasNext()) 1142 { 1143 attrBuffer.append(", "); 1144 attrBuffer.append(iterator.next()); 1145 } 1146 1147 attrs = attrBuffer.toString(); 1148 } 1149 1150 return new String[][] 1151 { 1152 new String[] { LOG_ELEMENT_BASE_DN, String.valueOf(rawBaseDN) }, 1153 new String[] { LOG_ELEMENT_SCOPE, String.valueOf(scope) }, 1154 new String[] { LOG_ELEMENT_SIZE_LIMIT, String.valueOf(sizeLimit) }, 1155 new String[] { LOG_ELEMENT_TIME_LIMIT, String.valueOf(timeLimit) }, 1156 new String[] { LOG_ELEMENT_FILTER, String.valueOf(rawFilter) }, 1157 new String[] { LOG_ELEMENT_REQUESTED_ATTRIBUTES, attrs } 1158 }; 1159 } 1160 1161 /** 1162 * {@inheritDoc} 1163 */ 1164 @Override() 1165 public final String[][] getResponseLogElements() 1166 { 1167 // Note that no debugging will be done in this method because it is a likely 1168 // candidate for being called by the logging subsystem. 1169 1170 String resultCode = String.valueOf(getResultCode().getIntValue()); 1171 1172 String errorMessage; 1173 MessageBuilder errorMessageBuffer = getErrorMessage(); 1174 if (errorMessageBuffer == null) 1175 { 1176 errorMessage = null; 1177 } 1178 else 1179 { 1180 errorMessage = errorMessageBuffer.toString(); 1181 } 1182 1183 String matchedDNStr; 1184 DN matchedDN = getMatchedDN(); 1185 if (matchedDN == null) 1186 { 1187 matchedDNStr = null; 1188 } 1189 else 1190 { 1191 matchedDNStr = matchedDN.toString(); 1192 } 1193 1194 String referrals; 1195 List<String> referralURLs = getReferralURLs(); 1196 if ((referralURLs == null) || referralURLs.isEmpty()) 1197 { 1198 referrals = null; 1199 } 1200 else 1201 { 1202 StringBuilder buffer = new StringBuilder(); 1203 Iterator<String> iterator = referralURLs.iterator(); 1204 buffer.append(iterator.next()); 1205 1206 while (iterator.hasNext()) 1207 { 1208 buffer.append(", "); 1209 buffer.append(iterator.next()); 1210 } 1211 1212 referrals = buffer.toString(); 1213 } 1214 1215 String processingTime = 1216 String.valueOf(processingStopTime - processingStartTime); 1217 1218 return new String[][] 1219 { 1220 new String[] { LOG_ELEMENT_RESULT_CODE, resultCode }, 1221 new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage }, 1222 new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr }, 1223 new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals }, 1224 new String[] { LOG_ELEMENT_ENTRIES_SENT, String.valueOf(entriesSent) }, 1225 new String[] { LOG_ELEMENT_REFERENCES_SENT, 1226 String.valueOf(referencesSent ) }, 1227 new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime } 1228 }; 1229 } 1230 1231 /** 1232 * {@inheritDoc} 1233 */ 1234 public DN getProxiedAuthorizationDN() 1235 { 1236 return proxiedAuthorizationDN; 1237 } 1238 1239 /** 1240 * {@inheritDoc} 1241 */ 1242 @Override() 1243 public final List<Control> getResponseControls() 1244 { 1245 return responseControls; 1246 } 1247 1248 /** 1249 * {@inheritDoc} 1250 */ 1251 @Override() 1252 public final void addResponseControl(Control control) 1253 { 1254 responseControls.add(control); 1255 } 1256 1257 /** 1258 * {@inheritDoc} 1259 */ 1260 @Override() 1261 public final void removeResponseControl(Control control) 1262 { 1263 responseControls.remove(control); 1264 } 1265 1266 1267 1268 /** 1269 * {@inheritDoc} 1270 */ 1271 @Override() 1272 public void abort(CancelRequest cancelRequest) 1273 { 1274 if(cancelResult == null && this.cancelRequest == null) 1275 { 1276 this.cancelRequest = cancelRequest; 1277 1278 if (persistentSearch != null) 1279 { 1280 DirectoryServer.deregisterPersistentSearch(persistentSearch); 1281 persistentSearch = null; 1282 } 1283 } 1284 } 1285 1286 1287 1288 /** 1289 * {@inheritDoc} 1290 */ 1291 @Override() 1292 public final void toString(StringBuilder buffer) 1293 { 1294 buffer.append("SearchOperation(connID="); 1295 buffer.append(clientConnection.getConnectionID()); 1296 buffer.append(", opID="); 1297 buffer.append(operationID); 1298 buffer.append(", baseDN="); 1299 buffer.append(rawBaseDN); 1300 buffer.append(", scope="); 1301 buffer.append(scope.toString()); 1302 buffer.append(", filter="); 1303 buffer.append(rawFilter.toString()); 1304 buffer.append(")"); 1305 } 1306 1307 /** 1308 * {@inheritDoc} 1309 */ 1310 public void setTimeLimitExpiration(Long timeLimitExpiration){ 1311 this.timeLimitExpiration = timeLimitExpiration; 1312 } 1313 1314 /** 1315 * {@inheritDoc} 1316 */ 1317 public boolean isReturnLDAPSubentries() 1318 { 1319 return returnLDAPSubentries; 1320 } 1321 1322 /** 1323 * {@inheritDoc} 1324 */ 1325 public void setReturnLDAPSubentries(boolean returnLDAPSubentries) 1326 { 1327 this.returnLDAPSubentries = returnLDAPSubentries; 1328 } 1329 1330 /** 1331 * {@inheritDoc} 1332 */ 1333 public MatchedValuesControl getMatchedValuesControl() 1334 { 1335 return matchedValuesControl; 1336 } 1337 1338 /** 1339 * {@inheritDoc} 1340 */ 1341 public void setMatchedValuesControl(MatchedValuesControl controls) 1342 { 1343 this.matchedValuesControl = controls; 1344 } 1345 1346 /** 1347 * {@inheritDoc} 1348 */ 1349 public PersistentSearch getPersistentSearch() 1350 { 1351 return persistentSearch; 1352 } 1353 1354 /** 1355 * {@inheritDoc} 1356 */ 1357 public boolean isIncludeUsableControl() 1358 { 1359 return includeUsableControl; 1360 } 1361 1362 /** 1363 * {@inheritDoc} 1364 */ 1365 public void setIncludeUsableControl(boolean includeUsableControl) 1366 { 1367 this.includeUsableControl = includeUsableControl; 1368 } 1369 1370 /** 1371 * {@inheritDoc} 1372 */ 1373 public void setPersistentSearch(PersistentSearch psearch) 1374 { 1375 this.persistentSearch = psearch; 1376 } 1377 1378 /** 1379 * {@inheritDoc} 1380 */ 1381 public Long getTimeLimitExpiration() 1382 { 1383 return timeLimitExpiration; 1384 } 1385 1386 /** 1387 * {@inheritDoc} 1388 */ 1389 public boolean isClientAcceptsReferrals() 1390 { 1391 return clientAcceptsReferrals; 1392 } 1393 1394 /** 1395 * {@inheritDoc} 1396 */ 1397 public void setClientAcceptsReferrals(boolean clientAcceptReferrals) 1398 { 1399 this.clientAcceptsReferrals = clientAcceptReferrals; 1400 } 1401 1402 /** 1403 * {@inheritDoc} 1404 */ 1405 public void incrementEntriesSent() 1406 { 1407 entriesSent++; 1408 } 1409 1410 /** 1411 * {@inheritDoc} 1412 */ 1413 public void incrementReferencesSent() 1414 { 1415 referencesSent++; 1416 } 1417 1418 /** 1419 * {@inheritDoc} 1420 */ 1421 public boolean isSendResponse() 1422 { 1423 return sendResponse; 1424 } 1425 1426 /** 1427 * {@inheritDoc} 1428 */ 1429 public void setSendResponse(boolean sendResponse) 1430 { 1431 this.sendResponse = sendResponse; 1432 1433 } 1434 1435 /** 1436 * {@inheritDoc} 1437 */ 1438 public boolean isRealAttributesOnly() 1439 { 1440 return this.realAttributesOnly; 1441 } 1442 1443 /** 1444 * {@inheritDoc} 1445 */ 1446 public boolean isVirtualAttributesOnly() 1447 { 1448 return this.virtualAttributesOnly; 1449 } 1450 1451 /** 1452 * {@inheritDoc} 1453 */ 1454 public void setRealAttributesOnly(boolean realAttributesOnly) 1455 { 1456 this.realAttributesOnly = realAttributesOnly; 1457 } 1458 1459 /** 1460 * {@inheritDoc} 1461 */ 1462 public void setVirtualAttributesOnly(boolean virtualAttributesOnly) 1463 { 1464 this.virtualAttributesOnly = virtualAttributesOnly; 1465 } 1466 1467 /** 1468 * {@inheritDoc} 1469 */ 1470 public void sendSearchEntry(SearchResultEntry searchEntry) 1471 throws DirectoryException 1472 { 1473 getClientConnection().sendSearchEntry(this, searchEntry); 1474 } 1475 1476 /** 1477 * {@inheritDoc} 1478 */ 1479 public boolean sendSearchReference(SearchResultReference searchReference) 1480 throws DirectoryException 1481 { 1482 return getClientConnection().sendSearchReference(this, searchReference); 1483 } 1484 1485 /** 1486 * {@inheritDoc} 1487 */ 1488 public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) 1489 { 1490 this.proxiedAuthorizationDN = proxiedAuthorizationDN; 1491 } 1492 1493 /** 1494 * {@inheritDoc} 1495 */ 1496 public final void run() 1497 { 1498 setResultCode(ResultCode.UNDEFINED); 1499 1500 // Start the processing timer. 1501 setProcessingStartTime(); 1502 1503 // Log the search request message. 1504 logSearchRequest(this); 1505 1506 setSendResponse(true); 1507 1508 // Get the plugin config manager that will be used for invoking plugins. 1509 PluginConfigManager pluginConfigManager = 1510 DirectoryServer.getPluginConfigManager(); 1511 1512 int timeLimit = getTimeLimit(); 1513 Long timeLimitExpiration; 1514 if (timeLimit <= 0) 1515 { 1516 timeLimitExpiration = Long.MAX_VALUE; 1517 } 1518 else 1519 { 1520 // FIXME -- Factor in the user's effective time limit. 1521 timeLimitExpiration = 1522 getProcessingStartTime() + (1000L * timeLimit); 1523 } 1524 setTimeLimitExpiration(timeLimitExpiration); 1525 1526 try 1527 { 1528 // Check for and handle a request to cancel this operation. 1529 checkIfCanceled(false); 1530 1531 PluginResult.PreParse preParseResult = 1532 pluginConfigManager.invokePreParseSearchPlugins(this); 1533 1534 if(!preParseResult.continueProcessing()) 1535 { 1536 setResultCode(preParseResult.getResultCode()); 1537 appendErrorMessage(preParseResult.getErrorMessage()); 1538 setMatchedDN(preParseResult.getMatchedDN()); 1539 setReferralURLs(preParseResult.getReferralURLs()); 1540 return; 1541 } 1542 1543 // Check for and handle a request to cancel this operation. 1544 checkIfCanceled(false); 1545 1546 // Process the search base and filter to convert them from their raw forms 1547 // as provided by the client to the forms required for the rest of the 1548 // search processing. 1549 DN baseDN = getBaseDN(); 1550 if (baseDN == null){ 1551 return; 1552 } 1553 1554 1555 // Retrieve the network group attached to the client connection 1556 // and get a workflow to process the operation. 1557 NetworkGroup ng = getClientConnection().getNetworkGroup(); 1558 Workflow workflow = ng.getWorkflowCandidate(baseDN); 1559 if (workflow == null) 1560 { 1561 // We have found no workflow for the requested base DN, just return 1562 // a no such entry result code and stop the processing. 1563 updateOperationErrMsgAndResCode(); 1564 return; 1565 } 1566 workflow.execute(this); 1567 } 1568 catch(CanceledOperationException coe) 1569 { 1570 if (debugEnabled()) 1571 { 1572 TRACER.debugCaught(DebugLogLevel.ERROR, coe); 1573 } 1574 1575 setResultCode(ResultCode.CANCELED); 1576 cancelResult = new CancelResult(ResultCode.CANCELED, null); 1577 1578 appendErrorMessage(coe.getCancelRequest().getCancelReason()); 1579 } 1580 finally 1581 { 1582 // Stop the processing timer. 1583 setProcessingStopTime(); 1584 1585 if(cancelRequest == null || cancelResult == null || 1586 cancelResult.getResultCode() != ResultCode.CANCELED) 1587 { 1588 // If everything is successful to this point and it is not a persistent 1589 // search, then send the search result done message to the client. 1590 // Otherwise, we'll want to make the size and time limit values 1591 // unlimited to ensure that the remainder of the persistent search 1592 // isn't subject to those restrictions. 1593 if (isSendResponse()) 1594 { 1595 sendSearchResultDone(); 1596 } 1597 else 1598 { 1599 setSizeLimit(0); 1600 setTimeLimit(0); 1601 } 1602 } 1603 else if(cancelRequest.notifyOriginalRequestor() || 1604 DirectoryServer.notifyAbandonedOperations()) 1605 { 1606 sendSearchResultDone(); 1607 } 1608 1609 // If no cancel result, set it 1610 if(cancelResult == null) 1611 { 1612 cancelResult = new CancelResult(ResultCode.TOO_LATE, null); 1613 } 1614 } 1615 } 1616 1617 1618 /** 1619 * Invokes the post response plugins. 1620 */ 1621 private void invokePostResponsePlugins() 1622 { 1623 // Get the plugin config manager that will be used for invoking plugins. 1624 PluginConfigManager pluginConfigManager = 1625 DirectoryServer.getPluginConfigManager(); 1626 1627 // Invoke the post response plugins that have been registered with 1628 // the current operation 1629 pluginConfigManager.invokePostResponseSearchPlugins(this); 1630 } 1631 1632 1633 /** 1634 * Updates the error message and the result code of the operation. 1635 * 1636 * This method is called because no workflows were found to process 1637 * the operation. 1638 */ 1639 private void updateOperationErrMsgAndResCode() 1640 { 1641 setResultCode(ResultCode.NO_SUCH_OBJECT); 1642 Message message = 1643 ERR_SEARCH_BASE_DOESNT_EXIST.get(String.valueOf(getBaseDN())); 1644 appendErrorMessage(message); 1645 } 1646 1647 }