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