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