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