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}