001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.util.Timer;
026    import java.util.concurrent.LinkedBlockingQueue;
027    import java.util.concurrent.TimeUnit;
028    
029    import com.unboundid.asn1.ASN1Buffer;
030    import com.unboundid.asn1.ASN1BufferSequence;
031    import com.unboundid.asn1.ASN1Element;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.protocol.LDAPMessage;
035    import com.unboundid.ldap.protocol.LDAPResponse;
036    import com.unboundid.ldap.protocol.ProtocolOp;
037    import com.unboundid.util.InternalUseOnly;
038    import com.unboundid.util.Mutable;
039    import com.unboundid.util.ThreadSafety;
040    import com.unboundid.util.ThreadSafetyLevel;
041    
042    import static com.unboundid.ldap.sdk.LDAPMessages.*;
043    import static com.unboundid.util.Debug.*;
044    import static com.unboundid.util.StaticUtils.*;
045    import 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)
100    public 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    }