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}