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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Timer;
028import java.util.concurrent.LinkedBlockingQueue;
029import java.util.concurrent.TimeUnit;
030
031import com.unboundid.asn1.ASN1Buffer;
032import com.unboundid.asn1.ASN1BufferSequence;
033import com.unboundid.asn1.ASN1Element;
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.asn1.ASN1Sequence;
036import com.unboundid.ldap.protocol.LDAPMessage;
037import com.unboundid.ldap.protocol.LDAPResponse;
038import com.unboundid.ldap.protocol.ProtocolOp;
039import com.unboundid.util.InternalUseOnly;
040import com.unboundid.util.Mutable;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043
044import static com.unboundid.ldap.sdk.LDAPMessages.*;
045import static com.unboundid.util.Debug.*;
046import static com.unboundid.util.StaticUtils.*;
047import 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)
102public 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}