001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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 Thread.currentThread().interrupt(); 607 throw new LDAPException(ResultCode.LOCAL_ERROR, 608 ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie); 609 } 610 611 return handleResponse(connection, response, requestTime, depth, false); 612 } 613 finally 614 { 615 connection.deregisterResponseAcceptor(messageID); 616 } 617 } 618 619 620 621 /** 622 * Sends this compare request to the directory server over the provided 623 * connection and returns the message ID for the request. 624 * 625 * @param connection The connection to use to communicate with the 626 * directory server. 627 * @param resultListener The async result listener that is to be notified 628 * when the response is received. It may be 629 * {@code null} only if the result is to be processed 630 * by this class. 631 * 632 * @return The async request ID created for the operation, or {@code null} if 633 * the provided {@code resultListener} is {@code null} and the 634 * operation will not actually be processed asynchronously. 635 * 636 * @throws LDAPException If a problem occurs while sending the request. 637 */ 638 AsyncRequestID processAsync(final LDAPConnection connection, 639 final AsyncCompareResultListener resultListener) 640 throws LDAPException 641 { 642 // Create the LDAP message. 643 messageID = connection.nextMessageID(); 644 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 645 646 647 // If the provided async result listener is {@code null}, then we'll use 648 // this class as the message acceptor. Otherwise, create an async helper 649 // and use it as the message acceptor. 650 final AsyncRequestID asyncRequestID; 651 if (resultListener == null) 652 { 653 asyncRequestID = null; 654 connection.registerResponseAcceptor(messageID, this); 655 } 656 else 657 { 658 final AsyncCompareHelper compareHelper = 659 new AsyncCompareHelper(connection, messageID, resultListener, 660 getIntermediateResponseListener()); 661 connection.registerResponseAcceptor(messageID, compareHelper); 662 asyncRequestID = compareHelper.getAsyncRequestID(); 663 664 final long timeout = getResponseTimeoutMillis(connection); 665 if (timeout > 0L) 666 { 667 final Timer timer = connection.getTimer(); 668 final AsyncTimeoutTimerTask timerTask = 669 new AsyncTimeoutTimerTask(compareHelper); 670 timer.schedule(timerTask, timeout); 671 asyncRequestID.setTimerTask(timerTask); 672 } 673 } 674 675 676 // Send the request to the server. 677 try 678 { 679 debugLDAPRequest(this); 680 connection.getConnectionStatistics().incrementNumCompareRequests(); 681 connection.sendMessage(message); 682 return asyncRequestID; 683 } 684 catch (LDAPException le) 685 { 686 debugException(le); 687 688 connection.deregisterResponseAcceptor(messageID); 689 throw le; 690 } 691 } 692 693 694 695 /** 696 * Processes this compare operation in synchronous mode, in which the same 697 * thread will send the request and read the response. 698 * 699 * @param connection The connection to use to communicate with the directory 700 * server. 701 * @param depth The current referral depth for this request. It should 702 * always be one for the initial request, and should only 703 * be incremented when following referrals. 704 * @param allowRetry Indicates whether the request may be re-tried on a 705 * re-established connection if the initial attempt fails 706 * in a way that indicates the connection is no longer 707 * valid and autoReconnect is true. 708 * 709 * @return An LDAP result object that provides information about the result 710 * of the compare processing. 711 * 712 * @throws LDAPException If a problem occurs while sending the request or 713 * reading the response. 714 */ 715 private CompareResult processSync(final LDAPConnection connection, 716 final int depth, final boolean allowRetry) 717 throws LDAPException 718 { 719 // Create the LDAP message. 720 messageID = connection.nextMessageID(); 721 final LDAPMessage message = 722 new LDAPMessage(messageID, this, getControls()); 723 724 725 // Set the appropriate timeout on the socket. 726 try 727 { 728 connection.getConnectionInternals(true).getSocket().setSoTimeout( 729 (int) getResponseTimeoutMillis(connection)); 730 } 731 catch (Exception e) 732 { 733 debugException(e); 734 } 735 736 737 // Send the request to the server. 738 final long requestTime = System.nanoTime(); 739 debugLDAPRequest(this); 740 connection.getConnectionStatistics().incrementNumCompareRequests(); 741 try 742 { 743 connection.sendMessage(message); 744 } 745 catch (final LDAPException le) 746 { 747 debugException(le); 748 749 if (allowRetry) 750 { 751 final CompareResult retryResult = reconnectAndRetry(connection, depth, 752 le.getResultCode()); 753 if (retryResult != null) 754 { 755 return retryResult; 756 } 757 } 758 759 throw le; 760 } 761 762 while (true) 763 { 764 final LDAPResponse response; 765 try 766 { 767 response = connection.readResponse(messageID); 768 } 769 catch (final LDAPException le) 770 { 771 debugException(le); 772 773 if ((le.getResultCode() == ResultCode.TIMEOUT) && 774 connection.getConnectionOptions().abandonOnTimeout()) 775 { 776 connection.abandon(messageID); 777 } 778 779 if (allowRetry) 780 { 781 final CompareResult retryResult = reconnectAndRetry(connection, depth, 782 le.getResultCode()); 783 if (retryResult != null) 784 { 785 return retryResult; 786 } 787 } 788 789 throw le; 790 } 791 792 if (response instanceof IntermediateResponse) 793 { 794 final IntermediateResponseListener listener = 795 getIntermediateResponseListener(); 796 if (listener != null) 797 { 798 listener.intermediateResponseReturned( 799 (IntermediateResponse) response); 800 } 801 } 802 else 803 { 804 return handleResponse(connection, response, requestTime, depth, 805 allowRetry); 806 } 807 } 808 } 809 810 811 812 /** 813 * Performs the necessary processing for handling a response. 814 * 815 * @param connection The connection used to read the response. 816 * @param response The response to be processed. 817 * @param requestTime The time the request was sent to the server. 818 * @param depth The current referral depth for this request. It 819 * should always be one for the initial request, and 820 * should only be incremented when following referrals. 821 * @param allowRetry Indicates whether the request may be re-tried on a 822 * re-established connection if the initial attempt fails 823 * in a way that indicates the connection is no longer 824 * valid and autoReconnect is true. 825 * 826 * @return The compare result. 827 * 828 * @throws LDAPException If a problem occurs. 829 */ 830 private CompareResult handleResponse(final LDAPConnection connection, 831 final LDAPResponse response, 832 final long requestTime, final int depth, 833 final boolean allowRetry) 834 throws LDAPException 835 { 836 if (response == null) 837 { 838 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 839 if (connection.getConnectionOptions().abandonOnTimeout()) 840 { 841 connection.abandon(messageID); 842 } 843 844 throw new LDAPException(ResultCode.TIMEOUT, 845 ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 846 connection.getHostPort())); 847 } 848 849 connection.getConnectionStatistics().incrementNumCompareResponses( 850 System.nanoTime() - requestTime); 851 if (response instanceof ConnectionClosedResponse) 852 { 853 // The connection was closed while waiting for the response. 854 if (allowRetry) 855 { 856 final CompareResult retryResult = reconnectAndRetry(connection, depth, 857 ResultCode.SERVER_DOWN); 858 if (retryResult != null) 859 { 860 return retryResult; 861 } 862 } 863 864 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 865 final String message = ccr.getMessage(); 866 if (message == null) 867 { 868 throw new LDAPException(ccr.getResultCode(), 869 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get( 870 connection.getHostPort(), toString())); 871 } 872 else 873 { 874 throw new LDAPException(ccr.getResultCode(), 875 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get( 876 connection.getHostPort(), toString(), message)); 877 } 878 } 879 880 final CompareResult result; 881 if (response instanceof CompareResult) 882 { 883 result = (CompareResult) response; 884 } 885 else 886 { 887 result = new CompareResult((LDAPResult) response); 888 } 889 890 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 891 followReferrals(connection)) 892 { 893 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 894 { 895 return new CompareResult(messageID, 896 ResultCode.REFERRAL_LIMIT_EXCEEDED, 897 ERR_TOO_MANY_REFERRALS.get(), 898 result.getMatchedDN(), 899 result.getReferralURLs(), 900 result.getResponseControls()); 901 } 902 903 return followReferral(result, connection, depth); 904 } 905 else 906 { 907 if (allowRetry) 908 { 909 final CompareResult retryResult = reconnectAndRetry(connection, depth, 910 result.getResultCode()); 911 if (retryResult != null) 912 { 913 return retryResult; 914 } 915 } 916 917 return result; 918 } 919 } 920 921 922 923 /** 924 * Attempts to re-establish the connection and retry processing this request 925 * on it. 926 * 927 * @param connection The connection to be re-established. 928 * @param depth The current referral depth for this request. It should 929 * always be one for the initial request, and should only 930 * be incremented when following referrals. 931 * @param resultCode The result code for the previous operation attempt. 932 * 933 * @return The result from re-trying the compare, or {@code null} if it could 934 * not be re-tried. 935 */ 936 private CompareResult reconnectAndRetry(final LDAPConnection connection, 937 final int depth, 938 final ResultCode resultCode) 939 { 940 try 941 { 942 // We will only want to retry for certain result codes that indicate a 943 // connection problem. 944 switch (resultCode.intValue()) 945 { 946 case ResultCode.SERVER_DOWN_INT_VALUE: 947 case ResultCode.DECODING_ERROR_INT_VALUE: 948 case ResultCode.CONNECT_ERROR_INT_VALUE: 949 connection.reconnect(); 950 return processSync(connection, depth, false); 951 } 952 } 953 catch (final Exception e) 954 { 955 debugException(e); 956 } 957 958 return null; 959 } 960 961 962 963 /** 964 * Attempts to follow a referral to perform a compare operation in the target 965 * server. 966 * 967 * @param referralResult The LDAP result object containing information about 968 * the referral to follow. 969 * @param connection The connection on which the referral was received. 970 * @param depth The number of referrals followed in the course of 971 * processing this request. 972 * 973 * @return The result of attempting to process the compare operation by 974 * following the referral. 975 * 976 * @throws LDAPException If a problem occurs while attempting to establish 977 * the referral connection, sending the request, or 978 * reading the result. 979 */ 980 private CompareResult followReferral(final CompareResult referralResult, 981 final LDAPConnection connection, 982 final int depth) 983 throws LDAPException 984 { 985 for (final String urlString : referralResult.getReferralURLs()) 986 { 987 try 988 { 989 final LDAPURL referralURL = new LDAPURL(urlString); 990 final String host = referralURL.getHost(); 991 992 if (host == null) 993 { 994 // We can't handle a referral in which there is no host. 995 continue; 996 } 997 998 final CompareRequest compareRequest; 999 if (referralURL.baseDNProvided()) 1000 { 1001 compareRequest = new CompareRequest(referralURL.getBaseDN(), 1002 attributeName, assertionValue, 1003 getControls()); 1004 } 1005 else 1006 { 1007 compareRequest = this; 1008 } 1009 1010 final LDAPConnection referralConn = connection.getReferralConnector(). 1011 getReferralConnection(referralURL, connection); 1012 try 1013 { 1014 return compareRequest.process(referralConn, depth+1); 1015 } 1016 finally 1017 { 1018 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1019 referralConn.close(); 1020 } 1021 } 1022 catch (LDAPException le) 1023 { 1024 debugException(le); 1025 } 1026 } 1027 1028 // If we've gotten here, then we could not follow any of the referral URLs, 1029 // so we'll just return the original referral result. 1030 return referralResult; 1031 } 1032 1033 1034 1035 /** 1036 * {@inheritDoc} 1037 */ 1038 @InternalUseOnly() 1039 public void responseReceived(final LDAPResponse response) 1040 throws LDAPException 1041 { 1042 try 1043 { 1044 responseQueue.put(response); 1045 } 1046 catch (Exception e) 1047 { 1048 debugException(e); 1049 1050 if (e instanceof InterruptedException) 1051 { 1052 Thread.currentThread().interrupt(); 1053 } 1054 1055 throw new LDAPException(ResultCode.LOCAL_ERROR, 1056 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1057 } 1058 } 1059 1060 1061 1062 /** 1063 * {@inheritDoc} 1064 */ 1065 @Override() 1066 public int getLastMessageID() 1067 { 1068 return messageID; 1069 } 1070 1071 1072 1073 /** 1074 * {@inheritDoc} 1075 */ 1076 @Override() 1077 public OperationType getOperationType() 1078 { 1079 return OperationType.COMPARE; 1080 } 1081 1082 1083 1084 /** 1085 * {@inheritDoc} 1086 */ 1087 public CompareRequest duplicate() 1088 { 1089 return duplicate(getControls()); 1090 } 1091 1092 1093 1094 /** 1095 * {@inheritDoc} 1096 */ 1097 public CompareRequest duplicate(final Control[] controls) 1098 { 1099 final CompareRequest r = new CompareRequest(dn, attributeName, 1100 assertionValue.getValue(), controls); 1101 1102 if (followReferralsInternal() != null) 1103 { 1104 r.setFollowReferrals(followReferralsInternal()); 1105 } 1106 1107 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1108 1109 return r; 1110 } 1111 1112 1113 1114 /** 1115 * {@inheritDoc} 1116 */ 1117 @Override() 1118 public void toString(final StringBuilder buffer) 1119 { 1120 buffer.append("CompareRequest(dn='"); 1121 buffer.append(dn); 1122 buffer.append("', attr='"); 1123 buffer.append(attributeName); 1124 buffer.append("', value='"); 1125 buffer.append(assertionValue.stringValue()); 1126 buffer.append('\''); 1127 1128 final Control[] controls = getControls(); 1129 if (controls.length > 0) 1130 { 1131 buffer.append(", controls={"); 1132 for (int i=0; i < controls.length; i++) 1133 { 1134 if (i > 0) 1135 { 1136 buffer.append(", "); 1137 } 1138 1139 buffer.append(controls[i]); 1140 } 1141 buffer.append('}'); 1142 } 1143 1144 buffer.append(')'); 1145 } 1146 1147 1148 1149 /** 1150 * {@inheritDoc} 1151 */ 1152 public void toCode(final List<String> lineList, final String requestID, 1153 final int indentSpaces, final boolean includeProcessing) 1154 { 1155 // Create the arguments for the request variable. 1156 final ArrayList<ToCodeArgHelper> constructorArgs = 1157 new ArrayList<ToCodeArgHelper>(3); 1158 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1159 constructorArgs.add(ToCodeArgHelper.createString(attributeName, 1160 "Attribute Name")); 1161 1162 // If the attribute is one that we consider sensitive, then we'll use a 1163 // redacted value. Otherwise, try to use the string value if it's 1164 // printable, or a byte array value if it's not. 1165 if (isSensitiveToCodeAttribute(attributeName)) 1166 { 1167 constructorArgs.add(ToCodeArgHelper.createString("---redacted-value", 1168 "Assertion Value (Redacted because " + attributeName + " is " + 1169 "configured as a sensitive attribute)")); 1170 } 1171 else if (isPrintableString(assertionValue.getValue())) 1172 { 1173 constructorArgs.add(ToCodeArgHelper.createString( 1174 assertionValue.stringValue(), 1175 "Assertion Value")); 1176 } 1177 else 1178 { 1179 constructorArgs.add(ToCodeArgHelper.createByteArray( 1180 assertionValue.getValue(), true, 1181 "Assertion Value")); 1182 } 1183 1184 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest", 1185 requestID + "Request", "new CompareRequest", constructorArgs); 1186 1187 1188 // If there are any controls, then add them to the request. 1189 for (final Control c : getControls()) 1190 { 1191 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1192 requestID + "Request.addControl", 1193 ToCodeArgHelper.createControl(c, null)); 1194 } 1195 1196 1197 // Add lines for processing the request and obtaining the result. 1198 if (includeProcessing) 1199 { 1200 // Generate a string with the appropriate indent. 1201 final StringBuilder buffer = new StringBuilder(); 1202 for (int i=0; i < indentSpaces; i++) 1203 { 1204 buffer.append(' '); 1205 } 1206 final String indent = buffer.toString(); 1207 1208 lineList.add(""); 1209 lineList.add(indent + "try"); 1210 lineList.add(indent + '{'); 1211 lineList.add(indent + " CompareResult " + requestID + 1212 "Result = connection.compare(" + requestID + "Request);"); 1213 lineList.add(indent + " // The compare was processed successfully."); 1214 lineList.add(indent + " boolean compareMatched = " + requestID + 1215 "Result.compareMatched();"); 1216 lineList.add(indent + '}'); 1217 lineList.add(indent + "catch (LDAPException e)"); 1218 lineList.add(indent + '{'); 1219 lineList.add(indent + " // The compare failed. Maybe the following " + 1220 "will help explain why."); 1221 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1222 lineList.add(indent + " String message = e.getMessage();"); 1223 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1224 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1225 lineList.add(indent + " Control[] responseControls = " + 1226 "e.getResponseControls();"); 1227 lineList.add(indent + '}'); 1228 } 1229 } 1230}