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