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