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