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