001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 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.InternalUseOnly; 041import com.unboundid.util.Mutable; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044 045import static com.unboundid.ldap.sdk.LDAPMessages.*; 046import static com.unboundid.util.Debug.*; 047import static com.unboundid.util.StaticUtils.*; 048import static com.unboundid.util.Validator.*; 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<LDAPResponse>(); 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 = nanosToMillis(System.nanoTime() - requestTime); 836 if (connection.getConnectionOptions().abandonOnTimeout()) 837 { 838 connection.abandon(messageID); 839 } 840 841 throw new LDAPException(ResultCode.TIMEOUT, 842 ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn, 843 connection.getHostPort())); 844 } 845 846 connection.getConnectionStatistics().incrementNumCompareResponses( 847 System.nanoTime() - requestTime); 848 if (response instanceof ConnectionClosedResponse) 849 { 850 // The connection was closed while waiting for the response. 851 if (allowRetry) 852 { 853 final CompareResult retryResult = reconnectAndRetry(connection, depth, 854 ResultCode.SERVER_DOWN); 855 if (retryResult != null) 856 { 857 return retryResult; 858 } 859 } 860 861 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 862 final String message = ccr.getMessage(); 863 if (message == null) 864 { 865 throw new LDAPException(ccr.getResultCode(), 866 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get( 867 connection.getHostPort(), toString())); 868 } 869 else 870 { 871 throw new LDAPException(ccr.getResultCode(), 872 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get( 873 connection.getHostPort(), toString(), message)); 874 } 875 } 876 877 final CompareResult result; 878 if (response instanceof CompareResult) 879 { 880 result = (CompareResult) response; 881 } 882 else 883 { 884 result = new CompareResult((LDAPResult) response); 885 } 886 887 if ((result.getResultCode().equals(ResultCode.REFERRAL)) && 888 followReferrals(connection)) 889 { 890 if (depth >= connection.getConnectionOptions().getReferralHopLimit()) 891 { 892 return new CompareResult(messageID, 893 ResultCode.REFERRAL_LIMIT_EXCEEDED, 894 ERR_TOO_MANY_REFERRALS.get(), 895 result.getMatchedDN(), 896 result.getReferralURLs(), 897 result.getResponseControls()); 898 } 899 900 return followReferral(result, connection, depth); 901 } 902 else 903 { 904 if (allowRetry) 905 { 906 final CompareResult retryResult = reconnectAndRetry(connection, depth, 907 result.getResultCode()); 908 if (retryResult != null) 909 { 910 return retryResult; 911 } 912 } 913 914 return result; 915 } 916 } 917 918 919 920 /** 921 * Attempts to re-establish the connection and retry processing this request 922 * on it. 923 * 924 * @param connection The connection to be re-established. 925 * @param depth The current referral depth for this request. It should 926 * always be one for the initial request, and should only 927 * be incremented when following referrals. 928 * @param resultCode The result code for the previous operation attempt. 929 * 930 * @return The result from re-trying the compare, or {@code null} if it could 931 * not be re-tried. 932 */ 933 private CompareResult reconnectAndRetry(final LDAPConnection connection, 934 final int depth, 935 final ResultCode resultCode) 936 { 937 try 938 { 939 // We will only want to retry for certain result codes that indicate a 940 // connection problem. 941 switch (resultCode.intValue()) 942 { 943 case ResultCode.SERVER_DOWN_INT_VALUE: 944 case ResultCode.DECODING_ERROR_INT_VALUE: 945 case ResultCode.CONNECT_ERROR_INT_VALUE: 946 connection.reconnect(); 947 return processSync(connection, depth, false); 948 } 949 } 950 catch (final Exception e) 951 { 952 debugException(e); 953 } 954 955 return null; 956 } 957 958 959 960 /** 961 * Attempts to follow a referral to perform a compare operation in the target 962 * server. 963 * 964 * @param referralResult The LDAP result object containing information about 965 * the referral to follow. 966 * @param connection The connection on which the referral was received. 967 * @param depth The number of referrals followed in the course of 968 * processing this request. 969 * 970 * @return The result of attempting to process the compare operation by 971 * following the referral. 972 * 973 * @throws LDAPException If a problem occurs while attempting to establish 974 * the referral connection, sending the request, or 975 * reading the result. 976 */ 977 private CompareResult followReferral(final CompareResult referralResult, 978 final LDAPConnection connection, 979 final int depth) 980 throws LDAPException 981 { 982 for (final String urlString : referralResult.getReferralURLs()) 983 { 984 try 985 { 986 final LDAPURL referralURL = new LDAPURL(urlString); 987 final String host = referralURL.getHost(); 988 989 if (host == null) 990 { 991 // We can't handle a referral in which there is no host. 992 continue; 993 } 994 995 final CompareRequest compareRequest; 996 if (referralURL.baseDNProvided()) 997 { 998 compareRequest = new CompareRequest(referralURL.getBaseDN(), 999 attributeName, assertionValue, 1000 getControls()); 1001 } 1002 else 1003 { 1004 compareRequest = this; 1005 } 1006 1007 final LDAPConnection referralConn = getReferralConnector(connection). 1008 getReferralConnection(referralURL, connection); 1009 try 1010 { 1011 return compareRequest.process(referralConn, depth+1); 1012 } 1013 finally 1014 { 1015 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null); 1016 referralConn.close(); 1017 } 1018 } 1019 catch (final LDAPException le) 1020 { 1021 debugException(le); 1022 } 1023 } 1024 1025 // If we've gotten here, then we could not follow any of the referral URLs, 1026 // so we'll just return the original referral result. 1027 return referralResult; 1028 } 1029 1030 1031 1032 /** 1033 * {@inheritDoc} 1034 */ 1035 @InternalUseOnly() 1036 @Override() 1037 public void responseReceived(final LDAPResponse response) 1038 throws LDAPException 1039 { 1040 try 1041 { 1042 responseQueue.put(response); 1043 } 1044 catch (final Exception e) 1045 { 1046 debugException(e); 1047 1048 if (e instanceof InterruptedException) 1049 { 1050 Thread.currentThread().interrupt(); 1051 } 1052 1053 throw new LDAPException(ResultCode.LOCAL_ERROR, 1054 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 1055 } 1056 } 1057 1058 1059 1060 /** 1061 * {@inheritDoc} 1062 */ 1063 @Override() 1064 public int getLastMessageID() 1065 { 1066 return messageID; 1067 } 1068 1069 1070 1071 /** 1072 * {@inheritDoc} 1073 */ 1074 @Override() 1075 public OperationType getOperationType() 1076 { 1077 return OperationType.COMPARE; 1078 } 1079 1080 1081 1082 /** 1083 * {@inheritDoc} 1084 */ 1085 @Override() 1086 public CompareRequest duplicate() 1087 { 1088 return duplicate(getControls()); 1089 } 1090 1091 1092 1093 /** 1094 * {@inheritDoc} 1095 */ 1096 @Override() 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 if (getReferralConnectorInternal() != null) 1108 { 1109 r.setReferralConnector(getReferralConnectorInternal()); 1110 } 1111 1112 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1113 1114 return r; 1115 } 1116 1117 1118 1119 /** 1120 * {@inheritDoc} 1121 */ 1122 @Override() 1123 public void toString(final StringBuilder buffer) 1124 { 1125 buffer.append("CompareRequest(dn='"); 1126 buffer.append(dn); 1127 buffer.append("', attr='"); 1128 buffer.append(attributeName); 1129 buffer.append("', value='"); 1130 buffer.append(assertionValue.stringValue()); 1131 buffer.append('\''); 1132 1133 final Control[] controls = getControls(); 1134 if (controls.length > 0) 1135 { 1136 buffer.append(", controls={"); 1137 for (int i=0; i < controls.length; i++) 1138 { 1139 if (i > 0) 1140 { 1141 buffer.append(", "); 1142 } 1143 1144 buffer.append(controls[i]); 1145 } 1146 buffer.append('}'); 1147 } 1148 1149 buffer.append(')'); 1150 } 1151 1152 1153 1154 /** 1155 * {@inheritDoc} 1156 */ 1157 @Override() 1158 public void toCode(final List<String> lineList, final String requestID, 1159 final int indentSpaces, final boolean includeProcessing) 1160 { 1161 // Create the arguments for the request variable. 1162 final ArrayList<ToCodeArgHelper> constructorArgs = 1163 new ArrayList<ToCodeArgHelper>(3); 1164 constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN")); 1165 constructorArgs.add(ToCodeArgHelper.createString(attributeName, 1166 "Attribute Name")); 1167 1168 // If the attribute is one that we consider sensitive, then we'll use a 1169 // redacted value. Otherwise, try to use the string value if it's 1170 // printable, or a byte array value if it's not. 1171 if (isSensitiveToCodeAttribute(attributeName)) 1172 { 1173 constructorArgs.add(ToCodeArgHelper.createString("---redacted-value", 1174 "Assertion Value (Redacted because " + attributeName + " is " + 1175 "configured as a sensitive attribute)")); 1176 } 1177 else if (isPrintableString(assertionValue.getValue())) 1178 { 1179 constructorArgs.add(ToCodeArgHelper.createString( 1180 assertionValue.stringValue(), 1181 "Assertion Value")); 1182 } 1183 else 1184 { 1185 constructorArgs.add(ToCodeArgHelper.createByteArray( 1186 assertionValue.getValue(), true, 1187 "Assertion Value")); 1188 } 1189 1190 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest", 1191 requestID + "Request", "new CompareRequest", constructorArgs); 1192 1193 1194 // If there are any controls, then add them to the request. 1195 for (final Control c : getControls()) 1196 { 1197 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1198 requestID + "Request.addControl", 1199 ToCodeArgHelper.createControl(c, null)); 1200 } 1201 1202 1203 // Add lines for processing the request and obtaining the result. 1204 if (includeProcessing) 1205 { 1206 // Generate a string with the appropriate indent. 1207 final StringBuilder buffer = new StringBuilder(); 1208 for (int i=0; i < indentSpaces; i++) 1209 { 1210 buffer.append(' '); 1211 } 1212 final String indent = buffer.toString(); 1213 1214 lineList.add(""); 1215 lineList.add(indent + "try"); 1216 lineList.add(indent + '{'); 1217 lineList.add(indent + " CompareResult " + requestID + 1218 "Result = connection.compare(" + requestID + "Request);"); 1219 lineList.add(indent + " // The compare was processed successfully."); 1220 lineList.add(indent + " boolean compareMatched = " + requestID + 1221 "Result.compareMatched();"); 1222 lineList.add(indent + '}'); 1223 lineList.add(indent + "catch (LDAPException e)"); 1224 lineList.add(indent + '{'); 1225 lineList.add(indent + " // The compare failed. Maybe the following " + 1226 "will help explain why."); 1227 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 1228 lineList.add(indent + " String message = e.getMessage();"); 1229 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 1230 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 1231 lineList.add(indent + " Control[] responseControls = " + 1232 "e.getResponseControls();"); 1233 lineList.add(indent + '}'); 1234 } 1235 } 1236}