001/* 002 * Copyright 2007-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 Ping Identity Corporation 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 @Override() 366 public String getDN() 367 { 368 return dn; 369 } 370 371 372 373 /** 374 * Specifies the DN of the entry in which the comparison is to be performed. 375 * 376 * @param dn The DN of the entry in which the comparison is to be performed. 377 * It must not be {@code null}. 378 */ 379 public void setDN(final String dn) 380 { 381 ensureNotNull(dn); 382 383 this.dn = dn; 384 } 385 386 387 388 /** 389 * Specifies the DN of the entry in which the comparison is to be performed. 390 * 391 * @param dn The DN of the entry in which the comparison is to be performed. 392 * It must not be {@code null}. 393 */ 394 public void setDN(final DN dn) 395 { 396 ensureNotNull(dn); 397 398 this.dn = dn.toString(); 399 } 400 401 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override() 407 public String getAttributeName() 408 { 409 return attributeName; 410 } 411 412 413 414 /** 415 * Specifies the name of the attribute for which the comparison is to be 416 * performed. 417 * 418 * @param attributeName The name of the attribute for which the comparison 419 * is to be performed. It must not be {@code null}. 420 */ 421 public void setAttributeName(final String attributeName) 422 { 423 ensureNotNull(attributeName); 424 425 this.attributeName = attributeName; 426 } 427 428 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override() 434 public String getAssertionValue() 435 { 436 return assertionValue.stringValue(); 437 } 438 439 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override() 445 public byte[] getAssertionValueBytes() 446 { 447 return assertionValue.getValue(); 448 } 449 450 451 452 /** 453 * {@inheritDoc} 454 */ 455 @Override() 456 public ASN1OctetString getRawAssertionValue() 457 { 458 return assertionValue; 459 } 460 461 462 463 /** 464 * Specifies the assertion value to specify within the target entry. 465 * 466 * @param assertionValue The assertion value to specify within the target 467 * entry. It must not be {@code null}. 468 */ 469 public void setAssertionValue(final String assertionValue) 470 { 471 ensureNotNull(assertionValue); 472 473 this.assertionValue = new ASN1OctetString(assertionValue); 474 } 475 476 477 478 /** 479 * Specifies the assertion value to specify within the target entry. 480 * 481 * @param assertionValue The assertion value to specify within the target 482 * entry. It must not be {@code null}. 483 */ 484 public void setAssertionValue(final byte[] assertionValue) 485 { 486 ensureNotNull(assertionValue); 487 488 this.assertionValue = new ASN1OctetString(assertionValue); 489 } 490 491 492 493 /** 494 * Specifies the assertion value to specify within the target entry. 495 * 496 * @param assertionValue The assertion value to specify within the target 497 * entry. It must not be {@code null}. 498 */ 499 public void setAssertionValue(final ASN1OctetString assertionValue) 500 { 501 this.assertionValue = assertionValue; 502 } 503 504 505 506 /** 507 * {@inheritDoc} 508 */ 509 @Override() 510 public byte getProtocolOpType() 511 { 512 return LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST; 513 } 514 515 516 517 /** 518 * {@inheritDoc} 519 */ 520 @Override() 521 public void writeTo(final ASN1Buffer buffer) 522 { 523 final ASN1BufferSequence requestSequence = 524 buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST); 525 buffer.addOctetString(dn); 526 527 final ASN1BufferSequence avaSequence = buffer.beginSequence(); 528 buffer.addOctetString(attributeName); 529 buffer.addElement(assertionValue); 530 avaSequence.end(); 531 requestSequence.end(); 532 } 533 534 535 536 /** 537 * Encodes the compare request protocol op to an ASN.1 element. 538 * 539 * @return The ASN.1 element with the encoded compare request protocol op. 540 */ 541 @Override() 542 public ASN1Element encodeProtocolOp() 543 { 544 // Create the compare request protocol op. 545 final ASN1Element[] avaElements = 546 { 547 new ASN1OctetString(attributeName), 548 assertionValue 549 }; 550 551 final ASN1Element[] protocolOpElements = 552 { 553 new ASN1OctetString(dn), 554 new ASN1Sequence(avaElements) 555 }; 556 557 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST, 558 protocolOpElements); 559 } 560 561 562 563 /** 564 * Sends this delete request to the directory server over the provided 565 * connection and returns the associated response. 566 * 567 * @param connection The connection to use to communicate with the directory 568 * server. 569 * @param depth The current referral depth for this request. It should 570 * always be one for the initial request, and should only 571 * be incremented when following referrals. 572 * 573 * @return An LDAP result object that provides information about the result 574 * of the delete processing. 575 * 576 * @throws LDAPException If a problem occurs while sending the request or 577 * reading the response. 578 */ 579 @Override() 580 protected CompareResult process(final LDAPConnection connection, 581 final int depth) 582 throws LDAPException 583 { 584 if (connection.synchronousMode()) 585 { 586 @SuppressWarnings("deprecation") 587 final boolean autoReconnect = 588 connection.getConnectionOptions().autoReconnect(); 589 return processSync(connection, depth, autoReconnect); 590 } 591 592 final long requestTime = System.nanoTime(); 593 processAsync(connection, null); 594 595 try 596 { 597 // Wait for and process the response. 598 final LDAPResponse response; 599 try 600 { 601 final long responseTimeout = getResponseTimeoutMillis(connection); 602 if (responseTimeout > 0) 603 { 604 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 605 } 606 else 607 { 608 response = responseQueue.take(); 609 } 610 } 611 catch (final InterruptedException ie) 612 { 613 debugException(ie); 614 Thread.currentThread().interrupt(); 615 throw new LDAPException(ResultCode.LOCAL_ERROR, 616 ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie); 617 } 618 619 return handleResponse(connection, response, requestTime, depth, false); 620 } 621 finally 622 { 623 connection.deregisterResponseAcceptor(messageID); 624 } 625 } 626 627 628 629 /** 630 * Sends this compare request to the directory server over the provided 631 * connection and returns the message ID for the request. 632 * 633 * @param connection The connection to use to communicate with the 634 * directory server. 635 * @param resultListener The async result listener that is to be notified 636 * when the response is received. It may be 637 * {@code null} only if the result is to be processed 638 * by this class. 639 * 640 * @return The async request ID created for the operation, or {@code null} if 641 * the provided {@code resultListener} is {@code null} and the 642 * operation will not actually be processed asynchronously. 643 * 644 * @throws LDAPException If a problem occurs while sending the request. 645 */ 646 AsyncRequestID processAsync(final LDAPConnection connection, 647 final AsyncCompareResultListener resultListener) 648 throws LDAPException 649 { 650 // Create the LDAP message. 651 messageID = connection.nextMessageID(); 652 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 653 654 655 // If the provided async result listener is {@code null}, then we'll use 656 // this class as the message acceptor. Otherwise, create an async helper 657 // and use it as the message acceptor. 658 final AsyncRequestID asyncRequestID; 659 if (resultListener == null) 660 { 661 asyncRequestID = null; 662 connection.registerResponseAcceptor(messageID, this); 663 } 664 else 665 { 666 final AsyncCompareHelper compareHelper = 667 new AsyncCompareHelper(connection, messageID, resultListener, 668 getIntermediateResponseListener()); 669 connection.registerResponseAcceptor(messageID, compareHelper); 670 asyncRequestID = compareHelper.getAsyncRequestID(); 671 672 final long timeout = getResponseTimeoutMillis(connection); 673 if (timeout > 0L) 674 { 675 final Timer timer = connection.getTimer(); 676 final AsyncTimeoutTimerTask timerTask = 677 new AsyncTimeoutTimerTask(compareHelper); 678 timer.schedule(timerTask, timeout); 679 asyncRequestID.setTimerTask(timerTask); 680 } 681 } 682 683 684 // Send the request to the server. 685 try 686 { 687 debugLDAPRequest(this); 688 connection.getConnectionStatistics().incrementNumCompareRequests(); 689 connection.sendMessage(message); 690 return asyncRequestID; 691 } 692 catch (final LDAPException le) 693 { 694 debugException(le); 695 696 connection.deregisterResponseAcceptor(messageID); 697 throw le; 698 } 699 } 700 701 702 703 /** 704 * Processes this compare operation in synchronous mode, in which the same 705 * thread will send the request and read the response. 706 * 707 * @param connection The connection to use to communicate with the directory 708 * server. 709 * @param depth The current referral depth for this request. It should 710 * always be one for the initial request, and should only 711 * be incremented when following referrals. 712 * @param allowRetry Indicates whether the request may be re-tried on a 713 * re-established connection if the initial attempt fails 714 * in a way that indicates the connection is no longer 715 * valid and autoReconnect is true. 716 * 717 * @return An LDAP result object that provides information about the result 718 * of the compare processing. 719 * 720 * @throws LDAPException If a problem occurs while sending the request or 721 * reading the response. 722 */ 723 private CompareResult processSync(final LDAPConnection connection, 724 final int depth, final boolean allowRetry) 725 throws LDAPException 726 { 727 // Create the LDAP message. 728 messageID = connection.nextMessageID(); 729 final LDAPMessage message = 730 new LDAPMessage(messageID, this, getControls()); 731 732 733 // Set the appropriate timeout on the socket. 734 try 735 { 736 connection.getConnectionInternals(true).getSocket().setSoTimeout( 737 (int) getResponseTimeoutMillis(connection)); 738 } 739 catch (final Exception e) 740 { 741 debugException(e); 742 } 743 744 745 // Send the request to the server. 746 final long requestTime = System.nanoTime(); 747 debugLDAPRequest(this); 748 connection.getConnectionStatistics().incrementNumCompareRequests(); 749 try 750 { 751 connection.sendMessage(message); 752 } 753 catch (final LDAPException le) 754 { 755 debugException(le); 756 757 if (allowRetry) 758 { 759 final CompareResult retryResult = reconnectAndRetry(connection, depth, 760 le.getResultCode()); 761 if (retryResult != null) 762 { 763 return retryResult; 764 } 765 } 766 767 throw le; 768 } 769 770 while (true) 771 { 772 final LDAPResponse response; 773 try 774 { 775 response = connection.readResponse(messageID); 776 } 777 catch (final LDAPException le) 778 { 779 debugException(le); 780 781 if ((le.getResultCode() == ResultCode.TIMEOUT) && 782 connection.getConnectionOptions().abandonOnTimeout()) 783 { 784 connection.abandon(messageID); 785 } 786 787 if (allowRetry) 788 { 789 final CompareResult retryResult = reconnectAndRetry(connection, depth, 790 le.getResultCode()); 791 if (retryResult != null) 792 { 793 return retryResult; 794 } 795 } 796 797 throw le; 798 } 799 800 if (response instanceof IntermediateResponse) 801 { 802 final IntermediateResponseListener listener = 803 getIntermediateResponseListener(); 804 if (listener != null) 805 { 806 listener.intermediateResponseReturned( 807 (IntermediateResponse) response); 808 } 809 } 810 else 811 { 812 return handleResponse(connection, response, requestTime, depth, 813 allowRetry); 814 } 815 } 816 } 817 818 819 820 /** 821 * Performs the necessary processing for handling a response. 822 * 823 * @param connection The connection used to read the response. 824 * @param response The response to be processed. 825 * @param requestTime The time the request was sent to the server. 826 * @param depth The current referral depth for this request. It 827 * should always be one for the initial request, and 828 * should only be incremented when following referrals. 829 * @param allowRetry Indicates whether the request may be re-tried on a 830 * re-established connection if the initial attempt fails 831 * in a way that indicates the connection is no longer 832 * valid and autoReconnect is true. 833 * 834 * @return The compare result. 835 * 836 * @throws LDAPException If a problem occurs. 837 */ 838 private CompareResult handleResponse(final LDAPConnection connection, 839 final LDAPResponse response, 840 final long requestTime, final int depth, 841 final boolean allowRetry) 842 throws LDAPException 843 { 844 if (response == null) 845 { 846 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 847 if (connection.getConnectionOptions().abandonOnTimeout()) 848 { 849 connection.abandon(messageID); 850 } 851 852 throw new LDAPException(ResultCode.TIMEOUT, 853 ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 854 connection.getHostPort())); 855 } 856 857 connection.getConnectionStatistics().incrementNumCompareResponses( 858 System.nanoTime() - requestTime); 859 if (response instanceof ConnectionClosedResponse) 860 { 861 // The connection was closed while waiting for the response. 862 if (allowRetry) 863 { 864 final CompareResult retryResult = reconnectAndRetry(connection, depth, 865 ResultCode.SERVER_DOWN); 866 if (retryResult != null) 867 { 868 return retryResult; 869 } 870 } 871 872 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 873 final String message = ccr.getMessage(); 874 if (message == null) 875 { 876 throw new LDAPException(ccr.getResultCode(), 877 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get( 878 connection.getHostPort(), toString())); 879 } 880 else 881 { 882 throw new LDAPException(ccr.getResultCode(), 883 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get( 884 connection.getHostPort(), toString(), message)); 885 } 886 } 887 888 final CompareResult result; 889 if (response instanceof CompareResult) 890 { 891 result = (CompareResult) response; 892 } 893 else 894 { 895 result = new CompareResult((LDAPResult) response); 896 } 897 898 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 899 followReferrals(connection)) 900 { 901 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 902 { 903 return new CompareResult(messageID, 904 ResultCode.REFERRAL_LIMIT_EXCEEDED, 905 ERR_TOO_MANY_REFERRALS.get(), 906 result.getMatchedDN(), 907 result.getReferralURLs(), 908 result.getResponseControls()); 909 } 910 911 return followReferral(result, connection, depth); 912 } 913 else 914 { 915 if (allowRetry) 916 { 917 final CompareResult retryResult = reconnectAndRetry(connection, depth, 918 result.getResultCode()); 919 if (retryResult != null) 920 { 921 return retryResult; 922 } 923 } 924 925 return result; 926 } 927 } 928 929 930 931 /** 932 * Attempts to re-establish the connection and retry processing this request 933 * on it. 934 * 935 * @param connection The connection to be re-established. 936 * @param depth The current referral depth for this request. It should 937 * always be one for the initial request, and should only 938 * be incremented when following referrals. 939 * @param resultCode The result code for the previous operation attempt. 940 * 941 * @return The result from re-trying the compare, or {@code null} if it could 942 * not be re-tried. 943 */ 944 private CompareResult reconnectAndRetry(final LDAPConnection connection, 945 final int depth, 946 final ResultCode resultCode) 947 { 948 try 949 { 950 // We will only want to retry for certain result codes that indicate a 951 // connection problem. 952 switch (resultCode.intValue()) 953 { 954 case ResultCode.SERVER_DOWN_INT_VALUE: 955 case ResultCode.DECODING_ERROR_INT_VALUE: 956 case ResultCode.CONNECT_ERROR_INT_VALUE: 957 connection.reconnect(); 958 return processSync(connection, depth, false); 959 } 960 } 961 catch (final Exception e) 962 { 963 debugException(e); 964 } 965 966 return null; 967 } 968 969 970 971 /** 972 * Attempts to follow a referral to perform a compare operation in the target 973 * server. 974 * 975 * @param referralResult The LDAP result object containing information about 976 * the referral to follow. 977 * @param connection The connection on which the referral was received. 978 * @param depth The number of referrals followed in the course of 979 * processing this request. 980 * 981 * @return The result of attempting to process the compare operation by 982 * following the referral. 983 * 984 * @throws LDAPException If a problem occurs while attempting to establish 985 * the referral connection, sending the request, or 986 * reading the result. 987 */ 988 private CompareResult followReferral(final CompareResult referralResult, 989 final LDAPConnection connection, 990 final int depth) 991 throws LDAPException 992 { 993 for (final String urlString : referralResult.getReferralURLs()) 994 { 995 try 996 { 997 final LDAPURL referralURL = new LDAPURL(urlString); 998 final String host = referralURL.getHost(); 999 1000 if (host == null) 1001 { 1002 // We can't handle a referral in which there is no host. 1003 continue; 1004 } 1005 1006 final CompareRequest compareRequest; 1007 if (referralURL.baseDNProvided()) 1008 { 1009 compareRequest = new CompareRequest(referralURL.getBaseDN(), 1010 attributeName, assertionValue, 1011 getControls()); 1012 } 1013 else 1014 { 1015 compareRequest = this; 1016 } 1017 1018 final LDAPConnection referralConn = connection.getReferralConnector(). 1019 getReferralConnection(referralURL, connection); 1020 try 1021 { 1022 return compareRequest.process(referralConn, depth+1); 1023 } 1024 finally 1025 { 1026 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1027 referralConn.close(); 1028 } 1029 } 1030 catch (final LDAPException le) 1031 { 1032 debugException(le); 1033 } 1034 } 1035 1036 // If we've gotten here, then we could not follow any of the referral URLs, 1037 // so we'll just return the original referral result. 1038 return referralResult; 1039 } 1040 1041 1042 1043 /** 1044 * {@inheritDoc} 1045 */ 1046 @InternalUseOnly() 1047 @Override() 1048 public void responseReceived(final LDAPResponse response) 1049 throws LDAPException 1050 { 1051 try 1052 { 1053 responseQueue.put(response); 1054 } 1055 catch (final Exception e) 1056 { 1057 debugException(e); 1058 1059 if (e instanceof InterruptedException) 1060 { 1061 Thread.currentThread().interrupt(); 1062 } 1063 1064 throw new LDAPException(ResultCode.LOCAL_ERROR, 1065 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1066 } 1067 } 1068 1069 1070 1071 /** 1072 * {@inheritDoc} 1073 */ 1074 @Override() 1075 public int getLastMessageID() 1076 { 1077 return messageID; 1078 } 1079 1080 1081 1082 /** 1083 * {@inheritDoc} 1084 */ 1085 @Override() 1086 public OperationType getOperationType() 1087 { 1088 return OperationType.COMPARE; 1089 } 1090 1091 1092 1093 /** 1094 * {@inheritDoc} 1095 */ 1096 @Override() 1097 public CompareRequest duplicate() 1098 { 1099 return duplicate(getControls()); 1100 } 1101 1102 1103 1104 /** 1105 * {@inheritDoc} 1106 */ 1107 @Override() 1108 public CompareRequest duplicate(final Control[] controls) 1109 { 1110 final CompareRequest r = new CompareRequest(dn, attributeName, 1111 assertionValue.getValue(), controls); 1112 1113 if (followReferralsInternal() != null) 1114 { 1115 r.setFollowReferrals(followReferralsInternal()); 1116 } 1117 1118 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1119 1120 return r; 1121 } 1122 1123 1124 1125 /** 1126 * {@inheritDoc} 1127 */ 1128 @Override() 1129 public void toString(final StringBuilder buffer) 1130 { 1131 buffer.append("CompareRequest(dn='"); 1132 buffer.append(dn); 1133 buffer.append("', attr='"); 1134 buffer.append(attributeName); 1135 buffer.append("', value='"); 1136 buffer.append(assertionValue.stringValue()); 1137 buffer.append('\''); 1138 1139 final Control[] controls = getControls(); 1140 if (controls.length > 0) 1141 { 1142 buffer.append(", controls={"); 1143 for (int i=0; i < controls.length; i++) 1144 { 1145 if (i > 0) 1146 { 1147 buffer.append(", "); 1148 } 1149 1150 buffer.append(controls[i]); 1151 } 1152 buffer.append('}'); 1153 } 1154 1155 buffer.append(')'); 1156 } 1157 1158 1159 1160 /** 1161 * {@inheritDoc} 1162 */ 1163 @Override() 1164 public void toCode(final List<String> lineList, final String requestID, 1165 final int indentSpaces, final boolean includeProcessing) 1166 { 1167 // Create the arguments for the request variable. 1168 final ArrayList<ToCodeArgHelper> constructorArgs = 1169 new ArrayList<ToCodeArgHelper>(3); 1170 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1171 constructorArgs.add(ToCodeArgHelper.createString(attributeName, 1172 "Attribute Name")); 1173 1174 // If the attribute is one that we consider sensitive, then we'll use a 1175 // redacted value. Otherwise, try to use the string value if it's 1176 // printable, or a byte array value if it's not. 1177 if (isSensitiveToCodeAttribute(attributeName)) 1178 { 1179 constructorArgs.add(ToCodeArgHelper.createString("---redacted-value", 1180 "Assertion Value (Redacted because " + attributeName + " is " + 1181 "configured as a sensitive attribute)")); 1182 } 1183 else if (isPrintableString(assertionValue.getValue())) 1184 { 1185 constructorArgs.add(ToCodeArgHelper.createString( 1186 assertionValue.stringValue(), 1187 "Assertion Value")); 1188 } 1189 else 1190 { 1191 constructorArgs.add(ToCodeArgHelper.createByteArray( 1192 assertionValue.getValue(), true, 1193 "Assertion Value")); 1194 } 1195 1196 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest", 1197 requestID + "Request", "new CompareRequest", constructorArgs); 1198 1199 1200 // If there are any controls, then add them to the request. 1201 for (final Control c : getControls()) 1202 { 1203 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1204 requestID + "Request.addControl", 1205 ToCodeArgHelper.createControl(c, null)); 1206 } 1207 1208 1209 // Add lines for processing the request and obtaining the result. 1210 if (includeProcessing) 1211 { 1212 // Generate a string with the appropriate indent. 1213 final StringBuilder buffer = new StringBuilder(); 1214 for (int i=0; i < indentSpaces; i++) 1215 { 1216 buffer.append(' '); 1217 } 1218 final String indent = buffer.toString(); 1219 1220 lineList.add(""); 1221 lineList.add(indent + "try"); 1222 lineList.add(indent + '{'); 1223 lineList.add(indent + " CompareResult " + requestID + 1224 "Result = connection.compare(" + requestID + "Request);"); 1225 lineList.add(indent + " // The compare was processed successfully."); 1226 lineList.add(indent + " boolean compareMatched = " + requestID + 1227 "Result.compareMatched();"); 1228 lineList.add(indent + '}'); 1229 lineList.add(indent + "catch (LDAPException e)"); 1230 lineList.add(indent + '{'); 1231 lineList.add(indent + " // The compare failed. Maybe the following " + 1232 "will help explain why."); 1233 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1234 lineList.add(indent + " String message = e.getMessage();"); 1235 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1236 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1237 lineList.add(indent + " Control[] responseControls = " + 1238 "e.getResponseControls();"); 1239 lineList.add(indent + '}'); 1240 } 1241 } 1242}