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