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