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