001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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        Thread.currentThread().interrupt();
607        throw new LDAPException(ResultCode.LOCAL_ERROR,
608             ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie);
609      }
610
611      return handleResponse(connection, response,  requestTime, depth, false);
612    }
613    finally
614    {
615      connection.deregisterResponseAcceptor(messageID);
616    }
617  }
618
619
620
621  /**
622   * Sends this compare request to the directory server over the provided
623   * connection and returns the message ID for the request.
624   *
625   * @param  connection      The connection to use to communicate with the
626   *                         directory server.
627   * @param  resultListener  The async result listener that is to be notified
628   *                         when the response is received.  It may be
629   *                         {@code null} only if the result is to be processed
630   *                         by this class.
631   *
632   * @return  The async request ID created for the operation, or {@code null} if
633   *          the provided {@code resultListener} is {@code null} and the
634   *          operation will not actually be processed asynchronously.
635   *
636   * @throws  LDAPException  If a problem occurs while sending the request.
637   */
638  AsyncRequestID processAsync(final LDAPConnection connection,
639                              final AsyncCompareResultListener resultListener)
640                 throws LDAPException
641  {
642    // Create the LDAP message.
643    messageID = connection.nextMessageID();
644    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
645
646
647    // If the provided async result listener is {@code null}, then we'll use
648    // this class as the message acceptor.  Otherwise, create an async helper
649    // and use it as the message acceptor.
650    final AsyncRequestID asyncRequestID;
651    if (resultListener == null)
652    {
653      asyncRequestID = null;
654      connection.registerResponseAcceptor(messageID, this);
655    }
656    else
657    {
658      final AsyncCompareHelper compareHelper =
659           new AsyncCompareHelper(connection, messageID, resultListener,
660                getIntermediateResponseListener());
661      connection.registerResponseAcceptor(messageID, compareHelper);
662      asyncRequestID = compareHelper.getAsyncRequestID();
663
664      final long timeout = getResponseTimeoutMillis(connection);
665      if (timeout > 0L)
666      {
667        final Timer timer = connection.getTimer();
668        final AsyncTimeoutTimerTask timerTask =
669             new AsyncTimeoutTimerTask(compareHelper);
670        timer.schedule(timerTask, timeout);
671        asyncRequestID.setTimerTask(timerTask);
672      }
673    }
674
675
676    // Send the request to the server.
677    try
678    {
679      debugLDAPRequest(this);
680      connection.getConnectionStatistics().incrementNumCompareRequests();
681      connection.sendMessage(message);
682      return asyncRequestID;
683    }
684    catch (LDAPException le)
685    {
686      debugException(le);
687
688      connection.deregisterResponseAcceptor(messageID);
689      throw le;
690    }
691  }
692
693
694
695  /**
696   * Processes this compare operation in synchronous mode, in which the same
697   * thread will send the request and read the response.
698   *
699   * @param  connection  The connection to use to communicate with the directory
700   *                     server.
701   * @param  depth       The current referral depth for this request.  It should
702   *                     always be one for the initial request, and should only
703   *                     be incremented when following referrals.
704   * @param  allowRetry   Indicates whether the request may be re-tried on a
705   *                      re-established connection if the initial attempt fails
706   *                      in a way that indicates the connection is no longer
707   *                      valid and autoReconnect is true.
708   *
709   * @return  An LDAP result object that provides information about the result
710   *          of the compare processing.
711   *
712   * @throws  LDAPException  If a problem occurs while sending the request or
713   *                         reading the response.
714   */
715  private CompareResult processSync(final LDAPConnection connection,
716                                    final int depth, final boolean allowRetry)
717          throws LDAPException
718  {
719    // Create the LDAP message.
720    messageID = connection.nextMessageID();
721    final LDAPMessage message =
722         new LDAPMessage(messageID,  this, getControls());
723
724
725    // Set the appropriate timeout on the socket.
726    try
727    {
728      connection.getConnectionInternals(true).getSocket().setSoTimeout(
729           (int) getResponseTimeoutMillis(connection));
730    }
731    catch (Exception e)
732    {
733      debugException(e);
734    }
735
736
737    // Send the request to the server.
738    final long requestTime = System.nanoTime();
739    debugLDAPRequest(this);
740    connection.getConnectionStatistics().incrementNumCompareRequests();
741    try
742    {
743      connection.sendMessage(message);
744    }
745    catch (final LDAPException le)
746    {
747      debugException(le);
748
749      if (allowRetry)
750      {
751        final CompareResult retryResult = reconnectAndRetry(connection, depth,
752             le.getResultCode());
753        if (retryResult != null)
754        {
755          return retryResult;
756        }
757      }
758
759      throw le;
760    }
761
762    while (true)
763    {
764      final LDAPResponse response;
765      try
766      {
767        response = connection.readResponse(messageID);
768      }
769      catch (final LDAPException le)
770      {
771        debugException(le);
772
773        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
774            connection.getConnectionOptions().abandonOnTimeout())
775        {
776          connection.abandon(messageID);
777        }
778
779        if (allowRetry)
780        {
781          final CompareResult retryResult = reconnectAndRetry(connection, depth,
782               le.getResultCode());
783          if (retryResult != null)
784          {
785            return retryResult;
786          }
787        }
788
789        throw le;
790      }
791
792      if (response instanceof IntermediateResponse)
793      {
794        final IntermediateResponseListener listener =
795             getIntermediateResponseListener();
796        if (listener != null)
797        {
798          listener.intermediateResponseReturned(
799               (IntermediateResponse) response);
800        }
801      }
802      else
803      {
804        return handleResponse(connection, response, requestTime, depth,
805             allowRetry);
806      }
807    }
808  }
809
810
811
812  /**
813   * Performs the necessary processing for handling a response.
814   *
815   * @param  connection   The connection used to read the response.
816   * @param  response     The response to be processed.
817   * @param  requestTime  The time the request was sent to the server.
818   * @param  depth        The current referral depth for this request.  It
819   *                      should always be one for the initial request, and
820   *                      should only be incremented when following referrals.
821   * @param  allowRetry   Indicates whether the request may be re-tried on a
822   *                      re-established connection if the initial attempt fails
823   *                      in a way that indicates the connection is no longer
824   *                      valid and autoReconnect is true.
825   *
826   * @return  The compare result.
827   *
828   * @throws  LDAPException  If a problem occurs.
829   */
830  private CompareResult handleResponse(final LDAPConnection connection,
831                                       final LDAPResponse response,
832                                       final long requestTime, final int depth,
833                                       final boolean allowRetry)
834          throws LDAPException
835  {
836    if (response == null)
837    {
838      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
839      if (connection.getConnectionOptions().abandonOnTimeout())
840      {
841        connection.abandon(messageID);
842      }
843
844      throw new LDAPException(ResultCode.TIMEOUT,
845           ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
846                connection.getHostPort()));
847    }
848
849    connection.getConnectionStatistics().incrementNumCompareResponses(
850         System.nanoTime() - requestTime);
851    if (response instanceof ConnectionClosedResponse)
852    {
853      // The connection was closed while waiting for the response.
854      if (allowRetry)
855      {
856        final CompareResult retryResult = reconnectAndRetry(connection, depth,
857             ResultCode.SERVER_DOWN);
858        if (retryResult != null)
859        {
860          return retryResult;
861        }
862      }
863
864      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
865      final String message = ccr.getMessage();
866      if (message == null)
867      {
868        throw new LDAPException(ccr.getResultCode(),
869             ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get(
870                  connection.getHostPort(), toString()));
871      }
872      else
873      {
874        throw new LDAPException(ccr.getResultCode(),
875             ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get(
876                  connection.getHostPort(), toString(), message));
877      }
878    }
879
880    final CompareResult result;
881    if (response instanceof CompareResult)
882    {
883      result = (CompareResult) response;
884    }
885    else
886    {
887      result = new CompareResult((LDAPResult) response);
888    }
889
890    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
891        followReferrals(connection))
892    {
893      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
894      {
895        return new CompareResult(messageID,
896                                 ResultCode.REFERRAL_LIMIT_EXCEEDED,
897                                 ERR_TOO_MANY_REFERRALS.get(),
898                                 result.getMatchedDN(),
899                                 result.getReferralURLs(),
900                                 result.getResponseControls());
901      }
902
903      return followReferral(result, connection, depth);
904    }
905    else
906    {
907      if (allowRetry)
908      {
909        final CompareResult retryResult = reconnectAndRetry(connection, depth,
910             result.getResultCode());
911        if (retryResult != null)
912        {
913          return retryResult;
914        }
915      }
916
917      return result;
918    }
919  }
920
921
922
923  /**
924   * Attempts to re-establish the connection and retry processing this request
925   * on it.
926   *
927   * @param  connection  The connection to be re-established.
928   * @param  depth       The current referral depth for this request.  It should
929   *                     always be one for the initial request, and should only
930   *                     be incremented when following referrals.
931   * @param  resultCode  The result code for the previous operation attempt.
932   *
933   * @return  The result from re-trying the compare, or {@code null} if it could
934   *          not be re-tried.
935   */
936  private CompareResult reconnectAndRetry(final LDAPConnection connection,
937                                          final int depth,
938                                          final ResultCode resultCode)
939  {
940    try
941    {
942      // We will only want to retry for certain result codes that indicate a
943      // connection problem.
944      switch (resultCode.intValue())
945      {
946        case ResultCode.SERVER_DOWN_INT_VALUE:
947        case ResultCode.DECODING_ERROR_INT_VALUE:
948        case ResultCode.CONNECT_ERROR_INT_VALUE:
949          connection.reconnect();
950          return processSync(connection, depth, false);
951      }
952    }
953    catch (final Exception e)
954    {
955      debugException(e);
956    }
957
958    return null;
959  }
960
961
962
963  /**
964   * Attempts to follow a referral to perform a compare operation in the target
965   * server.
966   *
967   * @param  referralResult  The LDAP result object containing information about
968   *                         the referral to follow.
969   * @param  connection      The connection on which the referral was received.
970   * @param  depth           The number of referrals followed in the course of
971   *                         processing this request.
972   *
973   * @return  The result of attempting to process the compare operation by
974   *          following the referral.
975   *
976   * @throws  LDAPException  If a problem occurs while attempting to establish
977   *                         the referral connection, sending the request, or
978   *                         reading the result.
979   */
980  private CompareResult followReferral(final CompareResult referralResult,
981                                       final LDAPConnection connection,
982                                       final int depth)
983          throws LDAPException
984  {
985    for (final String urlString : referralResult.getReferralURLs())
986    {
987      try
988      {
989        final LDAPURL referralURL = new LDAPURL(urlString);
990        final String host = referralURL.getHost();
991
992        if (host == null)
993        {
994          // We can't handle a referral in which there is no host.
995          continue;
996        }
997
998        final CompareRequest compareRequest;
999        if (referralURL.baseDNProvided())
1000        {
1001          compareRequest = new CompareRequest(referralURL.getBaseDN(),
1002                                              attributeName, assertionValue,
1003                                              getControls());
1004        }
1005        else
1006        {
1007          compareRequest = this;
1008        }
1009
1010        final LDAPConnection referralConn = connection.getReferralConnector().
1011             getReferralConnection(referralURL, connection);
1012        try
1013        {
1014          return compareRequest.process(referralConn, depth+1);
1015        }
1016        finally
1017        {
1018          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1019          referralConn.close();
1020        }
1021      }
1022      catch (LDAPException le)
1023      {
1024        debugException(le);
1025      }
1026    }
1027
1028    // If we've gotten here, then we could not follow any of the referral URLs,
1029    // so we'll just return the original referral result.
1030    return referralResult;
1031  }
1032
1033
1034
1035  /**
1036   * {@inheritDoc}
1037   */
1038  @InternalUseOnly()
1039  public void responseReceived(final LDAPResponse response)
1040         throws LDAPException
1041  {
1042    try
1043    {
1044      responseQueue.put(response);
1045    }
1046    catch (Exception e)
1047    {
1048      debugException(e);
1049
1050      if (e instanceof InterruptedException)
1051      {
1052        Thread.currentThread().interrupt();
1053      }
1054
1055      throw new LDAPException(ResultCode.LOCAL_ERROR,
1056           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1057    }
1058  }
1059
1060
1061
1062  /**
1063   * {@inheritDoc}
1064   */
1065  @Override()
1066  public int getLastMessageID()
1067  {
1068    return messageID;
1069  }
1070
1071
1072
1073  /**
1074   * {@inheritDoc}
1075   */
1076  @Override()
1077  public OperationType getOperationType()
1078  {
1079    return OperationType.COMPARE;
1080  }
1081
1082
1083
1084  /**
1085   * {@inheritDoc}
1086   */
1087  public CompareRequest duplicate()
1088  {
1089    return duplicate(getControls());
1090  }
1091
1092
1093
1094  /**
1095   * {@inheritDoc}
1096   */
1097  public CompareRequest duplicate(final Control[] controls)
1098  {
1099    final CompareRequest r = new CompareRequest(dn, attributeName,
1100         assertionValue.getValue(), controls);
1101
1102    if (followReferralsInternal() != null)
1103    {
1104      r.setFollowReferrals(followReferralsInternal());
1105    }
1106
1107    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1108
1109    return r;
1110  }
1111
1112
1113
1114  /**
1115   * {@inheritDoc}
1116   */
1117  @Override()
1118  public void toString(final StringBuilder buffer)
1119  {
1120    buffer.append("CompareRequest(dn='");
1121    buffer.append(dn);
1122    buffer.append("', attr='");
1123    buffer.append(attributeName);
1124    buffer.append("', value='");
1125    buffer.append(assertionValue.stringValue());
1126    buffer.append('\'');
1127
1128    final Control[] controls = getControls();
1129    if (controls.length > 0)
1130    {
1131      buffer.append(", controls={");
1132      for (int i=0; i < controls.length; i++)
1133      {
1134        if (i > 0)
1135        {
1136          buffer.append(", ");
1137        }
1138
1139        buffer.append(controls[i]);
1140      }
1141      buffer.append('}');
1142    }
1143
1144    buffer.append(')');
1145  }
1146
1147
1148
1149  /**
1150   * {@inheritDoc}
1151   */
1152  public void toCode(final List<String> lineList, final String requestID,
1153                     final int indentSpaces, final boolean includeProcessing)
1154  {
1155    // Create the arguments for the request variable.
1156    final ArrayList<ToCodeArgHelper> constructorArgs =
1157         new ArrayList<ToCodeArgHelper>(3);
1158    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1159    constructorArgs.add(ToCodeArgHelper.createString(attributeName,
1160         "Attribute Name"));
1161
1162    // If the attribute is one that we consider sensitive, then we'll use a
1163    // redacted value.  Otherwise, try to use the string value if it's
1164    // printable, or a byte array value if it's not.
1165    if (isSensitiveToCodeAttribute(attributeName))
1166    {
1167      constructorArgs.add(ToCodeArgHelper.createString("---redacted-value",
1168           "Assertion Value (Redacted because " + attributeName + " is " +
1169                "configured as a sensitive attribute)"));
1170    }
1171    else if (isPrintableString(assertionValue.getValue()))
1172    {
1173      constructorArgs.add(ToCodeArgHelper.createString(
1174           assertionValue.stringValue(),
1175           "Assertion Value"));
1176    }
1177    else
1178    {
1179      constructorArgs.add(ToCodeArgHelper.createByteArray(
1180           assertionValue.getValue(), true,
1181           "Assertion Value"));
1182    }
1183
1184    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest",
1185         requestID + "Request", "new CompareRequest", constructorArgs);
1186
1187
1188    // If there are any controls, then add them to the request.
1189    for (final Control c : getControls())
1190    {
1191      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1192           requestID + "Request.addControl",
1193           ToCodeArgHelper.createControl(c, null));
1194    }
1195
1196
1197    // Add lines for processing the request and obtaining the result.
1198    if (includeProcessing)
1199    {
1200      // Generate a string with the appropriate indent.
1201      final StringBuilder buffer = new StringBuilder();
1202      for (int i=0; i < indentSpaces; i++)
1203      {
1204        buffer.append(' ');
1205      }
1206      final String indent = buffer.toString();
1207
1208      lineList.add("");
1209      lineList.add(indent + "try");
1210      lineList.add(indent + '{');
1211      lineList.add(indent + "  CompareResult " + requestID +
1212           "Result = connection.compare(" + requestID + "Request);");
1213      lineList.add(indent + "  // The compare was processed successfully.");
1214      lineList.add(indent + "  boolean compareMatched = " + requestID +
1215           "Result.compareMatched();");
1216      lineList.add(indent + '}');
1217      lineList.add(indent + "catch (LDAPException e)");
1218      lineList.add(indent + '{');
1219      lineList.add(indent + "  // The compare failed.  Maybe the following " +
1220           "will help explain why.");
1221      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1222      lineList.add(indent + "  String message = e.getMessage();");
1223      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1224      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1225      lineList.add(indent + "  Control[] responseControls = " +
1226           "e.getResponseControls();");
1227      lineList.add(indent + '}');
1228    }
1229  }
1230}