001/* 002 * Copyright 2007-2016 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2016 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.ArrayList; 026import java.util.List; 027import java.util.Timer; 028import java.util.concurrent.LinkedBlockingQueue; 029import java.util.concurrent.TimeUnit; 030 031import com.unboundid.asn1.ASN1Buffer; 032import com.unboundid.asn1.ASN1BufferSequence; 033import com.unboundid.asn1.ASN1Element; 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.asn1.ASN1Sequence; 036import com.unboundid.ldap.protocol.LDAPMessage; 037import com.unboundid.ldap.protocol.LDAPResponse; 038import com.unboundid.ldap.protocol.ProtocolOp; 039import com.unboundid.util.InternalUseOnly; 040import com.unboundid.util.Mutable; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043 044import static com.unboundid.ldap.sdk.LDAPMessages.*; 045import static com.unboundid.util.Debug.*; 046import static com.unboundid.util.StaticUtils.*; 047import static com.unboundid.util.Validator.*; 048 049 050 051/** 052 * This class implements the processing necessary to perform an LDAPv3 compare 053 * operation, which may be used to determine whether a specified entry contains 054 * a given attribute value. Compare requests include the DN of the target 055 * entry, the name of the target attribute, and the value for which to make the 056 * determination. It may also include a set of controls to send to the server. 057 * <BR><BR> 058 * The assertion value may be specified as either a string or a byte array. If 059 * it is specified as a byte array, then it may represent either a binary or a 060 * string value. If a string value is provided as a byte array, then it should 061 * use the UTF-8 encoding for that value. 062 * <BR><BR> 063 * {@code CompareRequest} objects are mutable and therefore can be altered and 064 * re-used for multiple requests. Note, however, that {@code CompareRequest} 065 * objects are not threadsafe and therefore a single {@code CompareRequest} 066 * object instance should not be used to process multiple requests at the same 067 * time. 068 * <BR><BR> 069 * <H2>Example</H2> 070 * The following example demonstrates the process for performing a compare 071 * operation: 072 * <PRE> 073 * CompareRequest compareRequest = 074 * new CompareRequest("dc=example,dc=com", "description", "test"); 075 * CompareResult compareResult; 076 * try 077 * { 078 * compareResult = connection.compare(compareRequest); 079 * 080 * // The compare operation didn't throw an exception, so we can try to 081 * // determine whether the compare matched. 082 * if (compareResult.compareMatched()) 083 * { 084 * // The entry does have a description value of test. 085 * } 086 * else 087 * { 088 * // The entry does not have a description value of test. 089 * } 090 * } 091 * catch (LDAPException le) 092 * { 093 * // The compare operation failed. 094 * compareResult = new CompareResult(le.toLDAPResult()); 095 * ResultCode resultCode = le.getResultCode(); 096 * String errorMessageFromServer = le.getDiagnosticMessage(); 097 * } 098 * </PRE> 099 */ 100@Mutable() 101@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 102public final class CompareRequest 103 extends UpdatableLDAPRequest 104 implements ReadOnlyCompareRequest, ResponseAcceptor, ProtocolOp 105{ 106 /** 107 * The serial version UID for this serializable class. 108 */ 109 private static final long serialVersionUID = 6343453776330347024L; 110 111 112 113 // The queue that will be used to receive response messages from the server. 114 private final LinkedBlockingQueue<LDAPResponse> responseQueue = 115 new LinkedBlockingQueue<LDAPResponse>(); 116 117 // The assertion value for this compare request. 118 private ASN1OctetString assertionValue; 119 120 // The message ID from the last LDAP message sent from this request. 121 private int messageID = -1; 122 123 // The name of the target attribute. 124 private String attributeName; 125 126 // The DN of the entry in which the comparison is to be performed. 127 private String dn; 128 129 130 131 /** 132 * Creates a new compare request with the provided information. 133 * 134 * @param dn The DN of the entry in which the comparison is to 135 * be performed. It must not be {@code null}. 136 * @param attributeName The name of the target attribute for which the 137 * comparison is to be performed. It must not be 138 * {@code null}. 139 * @param assertionValue The assertion value to verify within the entry. It 140 * must not be {@code null}. 141 */ 142 public CompareRequest(final String dn, final String attributeName, 143 final String assertionValue) 144 { 145 super(null); 146 147 ensureNotNull(dn, attributeName, assertionValue); 148 149 this.dn = dn; 150 this.attributeName = attributeName; 151 this.assertionValue = new ASN1OctetString(assertionValue); 152 } 153 154 155 156 /** 157 * Creates a new compare request with the provided information. 158 * 159 * @param dn The DN of the entry in which the comparison is to 160 * be performed. It must not be {@code null}. 161 * @param attributeName The name of the target attribute for which the 162 * comparison is to be performed. It must not be 163 * {@code null}. 164 * @param assertionValue The assertion value to verify within the entry. It 165 * must not be {@code null}. 166 */ 167 public CompareRequest(final String dn, final String attributeName, 168 final byte[] assertionValue) 169 { 170 super(null); 171 172 ensureNotNull(dn, attributeName, assertionValue); 173 174 this.dn = dn; 175 this.attributeName = attributeName; 176 this.assertionValue = new ASN1OctetString(assertionValue); 177 } 178 179 180 181 /** 182 * Creates a new compare request with the provided information. 183 * 184 * @param dn The DN of the entry in which the comparison is to 185 * be performed. It must not be {@code null}. 186 * @param attributeName The name of the target attribute for which the 187 * comparison is to be performed. It must not be 188 * {@code null}. 189 * @param assertionValue The assertion value to verify within the entry. It 190 * must not be {@code null}. 191 */ 192 public CompareRequest(final DN dn, final String attributeName, 193 final String assertionValue) 194 { 195 super(null); 196 197 ensureNotNull(dn, attributeName, assertionValue); 198 199 this.dn = dn.toString(); 200 this.attributeName = attributeName; 201 this.assertionValue = new ASN1OctetString(assertionValue); 202 } 203 204 205 206 /** 207 * Creates a new compare request with the provided information. 208 * 209 * @param dn The DN of the entry in which the comparison is to 210 * be performed. It must not be {@code null}. 211 * @param attributeName The name of the target attribute for which the 212 * comparison is to be performed. It must not be 213 * {@code null}. 214 * @param assertionValue The assertion value to verify within the entry. It 215 * must not be {@code null}. 216 */ 217 public CompareRequest(final DN dn, final String attributeName, 218 final byte[] assertionValue) 219 { 220 super(null); 221 222 ensureNotNull(dn, attributeName, assertionValue); 223 224 this.dn = dn.toString(); 225 this.attributeName = attributeName; 226 this.assertionValue = new ASN1OctetString(assertionValue); 227 } 228 229 230 231 /** 232 * Creates a new compare request with the provided information. 233 * 234 * @param dn The DN of the entry in which the comparison is to 235 * be performed. It must not be {@code null}. 236 * @param attributeName The name of the target attribute for which the 237 * comparison is to be performed. It must not be 238 * {@code null}. 239 * @param assertionValue The assertion value to verify within the entry. It 240 * must not be {@code null}. 241 * @param controls The set of controls for this compare request. 242 */ 243 public CompareRequest(final String dn, final String attributeName, 244 final String assertionValue, final Control[] controls) 245 { 246 super(controls); 247 248 ensureNotNull(dn, attributeName, assertionValue); 249 250 this.dn = dn; 251 this.attributeName = attributeName; 252 this.assertionValue = new ASN1OctetString(assertionValue); 253 } 254 255 256 257 /** 258 * Creates a new compare request with the provided information. 259 * 260 * @param dn The DN of the entry in which the comparison is to 261 * be performed. It must not be {@code null}. 262 * @param attributeName The name of the target attribute for which the 263 * comparison is to be performed. It must not be 264 * {@code null}. 265 * @param assertionValue The assertion value to verify within the entry. It 266 * must not be {@code null}. 267 * @param controls The set of controls for this compare request. 268 */ 269 public CompareRequest(final String dn, final String attributeName, 270 final byte[] assertionValue, final Control[] controls) 271 { 272 super(controls); 273 274 ensureNotNull(dn, attributeName, assertionValue); 275 276 this.dn = dn; 277 this.attributeName = attributeName; 278 this.assertionValue = new ASN1OctetString(assertionValue); 279 } 280 281 282 283 /** 284 * Creates a new compare request with the provided information. 285 * 286 * @param dn The DN of the entry in which the comparison is to 287 * be performed. It must not be {@code null}. 288 * @param attributeName The name of the target attribute for which the 289 * comparison is to be performed. It must not be 290 * {@code null}. 291 * @param assertionValue The assertion value to verify within the entry. It 292 * must not be {@code null}. 293 * @param controls The set of controls for this compare request. 294 */ 295 public CompareRequest(final DN dn, final String attributeName, 296 final String assertionValue, final Control[] controls) 297 { 298 super(controls); 299 300 ensureNotNull(dn, attributeName, assertionValue); 301 302 this.dn = dn.toString(); 303 this.attributeName = attributeName; 304 this.assertionValue = new ASN1OctetString(assertionValue); 305 } 306 307 308 309 /** 310 * Creates a new compare request with the provided information. 311 * 312 * @param dn The DN of the entry in which the comparison is to 313 * be performed. It must not be {@code null}. 314 * @param attributeName The name of the target attribute for which the 315 * comparison is to be performed. It must not be 316 * {@code null}. 317 * @param assertionValue The assertion value to verify within the entry. It 318 * must not be {@code null}. 319 * @param controls The set of controls for this compare request. 320 */ 321 public CompareRequest(final DN dn, final String attributeName, 322 final ASN1OctetString assertionValue, 323 final Control[] controls) 324 { 325 super(controls); 326 327 ensureNotNull(dn, attributeName, assertionValue); 328 329 this.dn = dn.toString(); 330 this.attributeName = attributeName; 331 this.assertionValue = assertionValue; 332 } 333 334 335 336 /** 337 * Creates a new compare request with the provided information. 338 * 339 * @param dn The DN of the entry in which the comparison is to 340 * be performed. It must not be {@code null}. 341 * @param attributeName The name of the target attribute for which the 342 * comparison is to be performed. It must not be 343 * {@code null}. 344 * @param assertionValue The assertion value to verify within the entry. It 345 * must not be {@code null}. 346 * @param controls The set of controls for this compare request. 347 */ 348 public CompareRequest(final DN dn, final String attributeName, 349 final byte[] assertionValue, final Control[] controls) 350 { 351 super(controls); 352 353 ensureNotNull(dn, attributeName, assertionValue); 354 355 this.dn = dn.toString(); 356 this.attributeName = attributeName; 357 this.assertionValue = new ASN1OctetString(assertionValue); 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 public String getDN() 366 { 367 return dn; 368 } 369 370 371 372 /** 373 * Specifies the DN of the entry in which the comparison is to be performed. 374 * 375 * @param dn The DN of the entry in which the comparison is to be performed. 376 * It must not be {@code null}. 377 */ 378 public void setDN(final String dn) 379 { 380 ensureNotNull(dn); 381 382 this.dn = dn; 383 } 384 385 386 387 /** 388 * Specifies the DN of the entry in which the comparison is to be performed. 389 * 390 * @param dn The DN of the entry in which the comparison is to be performed. 391 * It must not be {@code null}. 392 */ 393 public void setDN(final DN dn) 394 { 395 ensureNotNull(dn); 396 397 this.dn = dn.toString(); 398 } 399 400 401 402 /** 403 * {@inheritDoc} 404 */ 405 public String getAttributeName() 406 { 407 return attributeName; 408 } 409 410 411 412 /** 413 * Specifies the name of the attribute for which the comparison is to be 414 * performed. 415 * 416 * @param attributeName The name of the attribute for which the comparison 417 * is to be performed. It must not be {@code null}. 418 */ 419 public void setAttributeName(final String attributeName) 420 { 421 ensureNotNull(attributeName); 422 423 this.attributeName = attributeName; 424 } 425 426 427 428 /** 429 * {@inheritDoc} 430 */ 431 public String getAssertionValue() 432 { 433 return assertionValue.stringValue(); 434 } 435 436 437 438 /** 439 * {@inheritDoc} 440 */ 441 public byte[] getAssertionValueBytes() 442 { 443 return assertionValue.getValue(); 444 } 445 446 447 448 /** 449 * {@inheritDoc} 450 */ 451 public ASN1OctetString getRawAssertionValue() 452 { 453 return assertionValue; 454 } 455 456 457 458 /** 459 * Specifies the assertion value to specify within the target entry. 460 * 461 * @param assertionValue The assertion value to specify within the target 462 * entry. It must not be {@code null}. 463 */ 464 public void setAssertionValue(final String assertionValue) 465 { 466 ensureNotNull(assertionValue); 467 468 this.assertionValue = new ASN1OctetString(assertionValue); 469 } 470 471 472 473 /** 474 * Specifies the assertion value to specify within the target entry. 475 * 476 * @param assertionValue The assertion value to specify within the target 477 * entry. It must not be {@code null}. 478 */ 479 public void setAssertionValue(final byte[] assertionValue) 480 { 481 ensureNotNull(assertionValue); 482 483 this.assertionValue = new ASN1OctetString(assertionValue); 484 } 485 486 487 488 /** 489 * Specifies the assertion value to specify within the target entry. 490 * 491 * @param assertionValue The assertion value to specify within the target 492 * entry. It must not be {@code null}. 493 */ 494 public void setAssertionValue(final ASN1OctetString assertionValue) 495 { 496 this.assertionValue = assertionValue; 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 public byte getProtocolOpType() 505 { 506 return LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST; 507 } 508 509 510 511 /** 512 * {@inheritDoc} 513 */ 514 public void writeTo(final ASN1Buffer buffer) 515 { 516 final ASN1BufferSequence requestSequence = 517 buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST); 518 buffer.addOctetString(dn); 519 520 final ASN1BufferSequence avaSequence = buffer.beginSequence(); 521 buffer.addOctetString(attributeName); 522 buffer.addElement(assertionValue); 523 avaSequence.end(); 524 requestSequence.end(); 525 } 526 527 528 529 /** 530 * Encodes the compare request protocol op to an ASN.1 element. 531 * 532 * @return The ASN.1 element with the encoded compare request protocol op. 533 */ 534 public ASN1Element encodeProtocolOp() 535 { 536 // Create the compare request protocol op. 537 final ASN1Element[] avaElements = 538 { 539 new ASN1OctetString(attributeName), 540 assertionValue 541 }; 542 543 final ASN1Element[] protocolOpElements = 544 { 545 new ASN1OctetString(dn), 546 new ASN1Sequence(avaElements) 547 }; 548 549 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST, 550 protocolOpElements); 551 } 552 553 554 555 /** 556 * Sends this delete request to the directory server over the provided 557 * connection and returns the associated response. 558 * 559 * @param connection The connection to use to communicate with the directory 560 * server. 561 * @param depth The current referral depth for this request. It should 562 * always be one for the initial request, and should only 563 * be incremented when following referrals. 564 * 565 * @return An LDAP result object that provides information about the result 566 * of the delete processing. 567 * 568 * @throws LDAPException If a problem occurs while sending the request or 569 * reading the response. 570 */ 571 @Override() 572 protected CompareResult process(final LDAPConnection connection, 573 final int depth) 574 throws LDAPException 575 { 576 if (connection.synchronousMode()) 577 { 578 @SuppressWarnings("deprecation") 579 final boolean autoReconnect = 580 connection.getConnectionOptions().autoReconnect(); 581 return processSync(connection, depth, autoReconnect); 582 } 583 584 final long requestTime = System.nanoTime(); 585 processAsync(connection, null); 586 587 try 588 { 589 // Wait for and process the response. 590 final LDAPResponse response; 591 try 592 { 593 final long responseTimeout = getResponseTimeoutMillis(connection); 594 if (responseTimeout > 0) 595 { 596 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 597 } 598 else 599 { 600 response = responseQueue.take(); 601 } 602 } 603 catch (InterruptedException ie) 604 { 605 debugException(ie); 606 throw new LDAPException(ResultCode.LOCAL_ERROR, 607 ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie); 608 } 609 610 return handleResponse(connection, response, requestTime, depth, false); 611 } 612 finally 613 { 614 connection.deregisterResponseAcceptor(messageID); 615 } 616 } 617 618 619 620 /** 621 * Sends this compare request to the directory server over the provided 622 * connection and returns the message ID for the request. 623 * 624 * @param connection The connection to use to communicate with the 625 * directory server. 626 * @param resultListener The async result listener that is to be notified 627 * when the response is received. It may be 628 * {@code null} only if the result is to be processed 629 * by this class. 630 * 631 * @return The async request ID created for the operation, or {@code null} if 632 * the provided {@code resultListener} is {@code null} and the 633 * operation will not actually be processed asynchronously. 634 * 635 * @throws LDAPException If a problem occurs while sending the request. 636 */ 637 AsyncRequestID processAsync(final LDAPConnection connection, 638 final AsyncCompareResultListener resultListener) 639 throws LDAPException 640 { 641 // Create the LDAP message. 642 messageID = connection.nextMessageID(); 643 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 644 645 646 // If the provided async result listener is {@code null}, then we'll use 647 // this class as the message acceptor. Otherwise, create an async helper 648 // and use it as the message acceptor. 649 final AsyncRequestID asyncRequestID; 650 if (resultListener == null) 651 { 652 asyncRequestID = null; 653 connection.registerResponseAcceptor(messageID, this); 654 } 655 else 656 { 657 final AsyncCompareHelper compareHelper = 658 new AsyncCompareHelper(connection, messageID, resultListener, 659 getIntermediateResponseListener()); 660 connection.registerResponseAcceptor(messageID, compareHelper); 661 asyncRequestID = compareHelper.getAsyncRequestID(); 662 663 final long timeout = getResponseTimeoutMillis(connection); 664 if (timeout > 0L) 665 { 666 final Timer timer = connection.getTimer(); 667 final AsyncTimeoutTimerTask timerTask = 668 new AsyncTimeoutTimerTask(compareHelper); 669 timer.schedule(timerTask, timeout); 670 asyncRequestID.setTimerTask(timerTask); 671 } 672 } 673 674 675 // Send the request to the server. 676 try 677 { 678 debugLDAPRequest(this); 679 connection.getConnectionStatistics().incrementNumCompareRequests(); 680 connection.sendMessage(message); 681 return asyncRequestID; 682 } 683 catch (LDAPException le) 684 { 685 debugException(le); 686 687 connection.deregisterResponseAcceptor(messageID); 688 throw le; 689 } 690 } 691 692 693 694 /** 695 * Processes this compare operation in synchronous mode, in which the same 696 * thread will send the request and read the response. 697 * 698 * @param connection The connection to use to communicate with the directory 699 * server. 700 * @param depth The current referral depth for this request. It should 701 * always be one for the initial request, and should only 702 * be incremented when following referrals. 703 * @param allowRetry Indicates whether the request may be re-tried on a 704 * re-established connection if the initial attempt fails 705 * in a way that indicates the connection is no longer 706 * valid and autoReconnect is true. 707 * 708 * @return An LDAP result object that provides information about the result 709 * of the compare processing. 710 * 711 * @throws LDAPException If a problem occurs while sending the request or 712 * reading the response. 713 */ 714 private CompareResult processSync(final LDAPConnection connection, 715 final int depth, final boolean allowRetry) 716 throws LDAPException 717 { 718 // Create the LDAP message. 719 messageID = connection.nextMessageID(); 720 final LDAPMessage message = 721 new LDAPMessage(messageID, this, getControls()); 722 723 724 // Set the appropriate timeout on the socket. 725 try 726 { 727 connection.getConnectionInternals(true).getSocket().setSoTimeout( 728 (int) getResponseTimeoutMillis(connection)); 729 } 730 catch (Exception e) 731 { 732 debugException(e); 733 } 734 735 736 // Send the request to the server. 737 final long requestTime = System.nanoTime(); 738 debugLDAPRequest(this); 739 connection.getConnectionStatistics().incrementNumCompareRequests(); 740 try 741 { 742 connection.sendMessage(message); 743 } 744 catch (final LDAPException le) 745 { 746 debugException(le); 747 748 if (allowRetry) 749 { 750 final CompareResult retryResult = reconnectAndRetry(connection, depth, 751 le.getResultCode()); 752 if (retryResult != null) 753 { 754 return retryResult; 755 } 756 } 757 758 throw le; 759 } 760 761 while (true) 762 { 763 final LDAPResponse response; 764 try 765 { 766 response = connection.readResponse(messageID); 767 } 768 catch (final LDAPException le) 769 { 770 debugException(le); 771 772 if ((le.getResultCode() == ResultCode.TIMEOUT) && 773 connection.getConnectionOptions().abandonOnTimeout()) 774 { 775 connection.abandon(messageID); 776 } 777 778 if (allowRetry) 779 { 780 final CompareResult retryResult = reconnectAndRetry(connection, depth, 781 le.getResultCode()); 782 if (retryResult != null) 783 { 784 return retryResult; 785 } 786 } 787 788 throw le; 789 } 790 791 if (response instanceof IntermediateResponse) 792 { 793 final IntermediateResponseListener listener = 794 getIntermediateResponseListener(); 795 if (listener != null) 796 { 797 listener.intermediateResponseReturned( 798 (IntermediateResponse) response); 799 } 800 } 801 else 802 { 803 return handleResponse(connection, response, requestTime, depth, 804 allowRetry); 805 } 806 } 807 } 808 809 810 811 /** 812 * Performs the necessary processing for handling a response. 813 * 814 * @param connection The connection used to read the response. 815 * @param response The response to be processed. 816 * @param requestTime The time the request was sent to the server. 817 * @param depth The current referral depth for this request. It 818 * should always be one for the initial request, and 819 * should only be incremented when following referrals. 820 * @param allowRetry Indicates whether the request may be re-tried on a 821 * re-established connection if the initial attempt fails 822 * in a way that indicates the connection is no longer 823 * valid and autoReconnect is true. 824 * 825 * @return The compare result. 826 * 827 * @throws LDAPException If a problem occurs. 828 */ 829 private CompareResult handleResponse(final LDAPConnection connection, 830 final LDAPResponse response, 831 final long requestTime, final int depth, 832 final boolean allowRetry) 833 throws LDAPException 834 { 835 if (response == null) 836 { 837 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 838 if (connection.getConnectionOptions().abandonOnTimeout()) 839 { 840 connection.abandon(messageID); 841 } 842 843 throw new LDAPException(ResultCode.TIMEOUT, 844 ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 845 connection.getHostPort())); 846 } 847 848 connection.getConnectionStatistics().incrementNumCompareResponses( 849 System.nanoTime() - requestTime); 850 if (response instanceof ConnectionClosedResponse) 851 { 852 // The connection was closed while waiting for the response. 853 if (allowRetry) 854 { 855 final CompareResult retryResult = reconnectAndRetry(connection, depth, 856 ResultCode.SERVER_DOWN); 857 if (retryResult != null) 858 { 859 return retryResult; 860 } 861 } 862 863 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 864 final String message = ccr.getMessage(); 865 if (message == null) 866 { 867 throw new LDAPException(ccr.getResultCode(), 868 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get( 869 connection.getHostPort(), toString())); 870 } 871 else 872 { 873 throw new LDAPException(ccr.getResultCode(), 874 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get( 875 connection.getHostPort(), toString(), message)); 876 } 877 } 878 879 final CompareResult result; 880 if (response instanceof CompareResult) 881 { 882 result = (CompareResult) response; 883 } 884 else 885 { 886 result = new CompareResult((LDAPResult) response); 887 } 888 889 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 890 followReferrals(connection)) 891 { 892 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 893 { 894 return new CompareResult(messageID, 895 ResultCode.REFERRAL_LIMIT_EXCEEDED, 896 ERR_TOO_MANY_REFERRALS.get(), 897 result.getMatchedDN(), 898 result.getReferralURLs(), 899 result.getResponseControls()); 900 } 901 902 return followReferral(result, connection, depth); 903 } 904 else 905 { 906 if (allowRetry) 907 { 908 final CompareResult retryResult = reconnectAndRetry(connection, depth, 909 result.getResultCode()); 910 if (retryResult != null) 911 { 912 return retryResult; 913 } 914 } 915 916 return result; 917 } 918 } 919 920 921 922 /** 923 * Attempts to re-establish the connection and retry processing this request 924 * on it. 925 * 926 * @param connection The connection to be re-established. 927 * @param depth The current referral depth for this request. It should 928 * always be one for the initial request, and should only 929 * be incremented when following referrals. 930 * @param resultCode The result code for the previous operation attempt. 931 * 932 * @return The result from re-trying the compare, or {@code null} if it could 933 * not be re-tried. 934 */ 935 private CompareResult reconnectAndRetry(final LDAPConnection connection, 936 final int depth, 937 final ResultCode resultCode) 938 { 939 try 940 { 941 // We will only want to retry for certain result codes that indicate a 942 // connection problem. 943 switch (resultCode.intValue()) 944 { 945 case ResultCode.SERVER_DOWN_INT_VALUE: 946 case ResultCode.DECODING_ERROR_INT_VALUE: 947 case ResultCode.CONNECT_ERROR_INT_VALUE: 948 connection.reconnect(); 949 return processSync(connection, depth, false); 950 } 951 } 952 catch (final Exception e) 953 { 954 debugException(e); 955 } 956 957 return null; 958 } 959 960 961 962 /** 963 * Attempts to follow a referral to perform a compare operation in the target 964 * server. 965 * 966 * @param referralResult The LDAP result object containing information about 967 * the referral to follow. 968 * @param connection The connection on which the referral was received. 969 * @param depth The number of referrals followed in the course of 970 * processing this request. 971 * 972 * @return The result of attempting to process the compare operation by 973 * following the referral. 974 * 975 * @throws LDAPException If a problem occurs while attempting to establish 976 * the referral connection, sending the request, or 977 * reading the result. 978 */ 979 private CompareResult followReferral(final CompareResult referralResult, 980 final LDAPConnection connection, 981 final int depth) 982 throws LDAPException 983 { 984 for (final String urlString : referralResult.getReferralURLs()) 985 { 986 try 987 { 988 final LDAPURL referralURL = new LDAPURL(urlString); 989 final String host = referralURL.getHost(); 990 991 if (host == null) 992 { 993 // We can't handle a referral in which there is no host. 994 continue; 995 } 996 997 final CompareRequest compareRequest; 998 if (referralURL.baseDNProvided()) 999 { 1000 compareRequest = new CompareRequest(referralURL.getBaseDN(), 1001 attributeName, assertionValue, 1002 getControls()); 1003 } 1004 else 1005 { 1006 compareRequest = this; 1007 } 1008 1009 final LDAPConnection referralConn = connection.getReferralConnector(). 1010 getReferralConnection(referralURL, connection); 1011 try 1012 { 1013 return compareRequest.process(referralConn, depth+1); 1014 } 1015 finally 1016 { 1017 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1018 referralConn.close(); 1019 } 1020 } 1021 catch (LDAPException le) 1022 { 1023 debugException(le); 1024 } 1025 } 1026 1027 // If we've gotten here, then we could not follow any of the referral URLs, 1028 // so we'll just return the original referral result. 1029 return referralResult; 1030 } 1031 1032 1033 1034 /** 1035 * {@inheritDoc} 1036 */ 1037 @InternalUseOnly() 1038 public void responseReceived(final LDAPResponse response) 1039 throws LDAPException 1040 { 1041 try 1042 { 1043 responseQueue.put(response); 1044 } 1045 catch (Exception e) 1046 { 1047 debugException(e); 1048 throw new LDAPException(ResultCode.LOCAL_ERROR, 1049 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1050 } 1051 } 1052 1053 1054 1055 /** 1056 * {@inheritDoc} 1057 */ 1058 @Override() 1059 public int getLastMessageID() 1060 { 1061 return messageID; 1062 } 1063 1064 1065 1066 /** 1067 * {@inheritDoc} 1068 */ 1069 @Override() 1070 public OperationType getOperationType() 1071 { 1072 return OperationType.COMPARE; 1073 } 1074 1075 1076 1077 /** 1078 * {@inheritDoc} 1079 */ 1080 public CompareRequest duplicate() 1081 { 1082 return duplicate(getControls()); 1083 } 1084 1085 1086 1087 /** 1088 * {@inheritDoc} 1089 */ 1090 public CompareRequest duplicate(final Control[] controls) 1091 { 1092 final CompareRequest r = new CompareRequest(dn, attributeName, 1093 assertionValue.getValue(), controls); 1094 1095 if (followReferralsInternal() != null) 1096 { 1097 r.setFollowReferrals(followReferralsInternal()); 1098 } 1099 1100 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1101 1102 return r; 1103 } 1104 1105 1106 1107 /** 1108 * {@inheritDoc} 1109 */ 1110 @Override() 1111 public void toString(final StringBuilder buffer) 1112 { 1113 buffer.append("CompareRequest(dn='"); 1114 buffer.append(dn); 1115 buffer.append("', attr='"); 1116 buffer.append(attributeName); 1117 buffer.append("', value='"); 1118 buffer.append(assertionValue.stringValue()); 1119 buffer.append('\''); 1120 1121 final Control[] controls = getControls(); 1122 if (controls.length > 0) 1123 { 1124 buffer.append(", controls={"); 1125 for (int i=0; i < controls.length; i++) 1126 { 1127 if (i > 0) 1128 { 1129 buffer.append(", "); 1130 } 1131 1132 buffer.append(controls[i]); 1133 } 1134 buffer.append('}'); 1135 } 1136 1137 buffer.append(')'); 1138 } 1139 1140 1141 1142 /** 1143 * {@inheritDoc} 1144 */ 1145 public void toCode(final List<String> lineList, final String requestID, 1146 final int indentSpaces, final boolean includeProcessing) 1147 { 1148 // Create the arguments for the request variable. 1149 final ArrayList<ToCodeArgHelper> constructorArgs = 1150 new ArrayList<ToCodeArgHelper>(3); 1151 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1152 constructorArgs.add(ToCodeArgHelper.createString(attributeName, 1153 "Attribute Name")); 1154 1155 // If the attribute is one that we consider sensitive, then we'll use a 1156 // redacted value. Otherwise, try to use the string value if it's 1157 // printable, or a byte array value if it's not. 1158 if (isSensitiveToCodeAttribute(attributeName)) 1159 { 1160 constructorArgs.add(ToCodeArgHelper.createString("---redacted-value", 1161 "Assertion Value (Redacted because " + attributeName + " is " + 1162 "configured as a sensitive attribute)")); 1163 } 1164 else if (isPrintableString(assertionValue.getValue())) 1165 { 1166 constructorArgs.add(ToCodeArgHelper.createString( 1167 assertionValue.stringValue(), 1168 "Assertion Value")); 1169 } 1170 else 1171 { 1172 constructorArgs.add(ToCodeArgHelper.createByteArray( 1173 assertionValue.getValue(), true, 1174 "Assertion Value")); 1175 } 1176 1177 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest", 1178 requestID + "Request", "new CompareRequest", constructorArgs); 1179 1180 1181 // If there are any controls, then add them to the request. 1182 for (final Control c : getControls()) 1183 { 1184 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1185 requestID + "Request.addControl", 1186 ToCodeArgHelper.createControl(c, null)); 1187 } 1188 1189 1190 // Add lines for processing the request and obtaining the result. 1191 if (includeProcessing) 1192 { 1193 // Generate a string with the appropriate indent. 1194 final StringBuilder buffer = new StringBuilder(); 1195 for (int i=0; i < indentSpaces; i++) 1196 { 1197 buffer.append(' '); 1198 } 1199 final String indent = buffer.toString(); 1200 1201 lineList.add(""); 1202 lineList.add(indent + "try"); 1203 lineList.add(indent + '{'); 1204 lineList.add(indent + " CompareResult " + requestID + 1205 "Result = connection.compare(" + requestID + "Request);"); 1206 lineList.add(indent + " // The compare was processed successfully."); 1207 lineList.add(indent + " boolean compareMatched = " + requestID + 1208 "Result.compareMatched();"); 1209 lineList.add(indent + '}'); 1210 lineList.add(indent + "catch (LDAPException e)"); 1211 lineList.add(indent + '{'); 1212 lineList.add(indent + " // The compare failed. Maybe the following " + 1213 "will help explain why."); 1214 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1215 lineList.add(indent + " String message = e.getMessage();"); 1216 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1217 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1218 lineList.add(indent + " Control[] responseControls = " + 1219 "e.getResponseControls();"); 1220 lineList.add(indent + '}'); 1221 } 1222 } 1223}