001/*
002 * Copyright 2007-2017 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 Ping Identity Corporation
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  @Override()
366  public String getDN()
367  {
368    return dn;
369  }
370
371
372
373  /**
374   * Specifies the DN of the entry in which the comparison is to be performed.
375   *
376   * @param  dn  The DN of the entry in which the comparison is to be performed.
377   *             It must not be {@code null}.
378   */
379  public void setDN(final String dn)
380  {
381    ensureNotNull(dn);
382
383    this.dn = dn;
384  }
385
386
387
388  /**
389   * Specifies the DN of the entry in which the comparison is to be performed.
390   *
391   * @param  dn  The DN of the entry in which the comparison is to be performed.
392   *             It must not be {@code null}.
393   */
394  public void setDN(final DN dn)
395  {
396    ensureNotNull(dn);
397
398    this.dn = dn.toString();
399  }
400
401
402
403  /**
404   * {@inheritDoc}
405   */
406  @Override()
407  public String getAttributeName()
408  {
409    return attributeName;
410  }
411
412
413
414  /**
415   * Specifies the name of the attribute for which the comparison is to be
416   * performed.
417   *
418   * @param  attributeName  The name of the attribute for which the comparison
419   *                        is to be performed.  It must not be {@code null}.
420   */
421  public void setAttributeName(final String attributeName)
422  {
423    ensureNotNull(attributeName);
424
425    this.attributeName = attributeName;
426  }
427
428
429
430  /**
431   * {@inheritDoc}
432   */
433  @Override()
434  public String getAssertionValue()
435  {
436    return assertionValue.stringValue();
437  }
438
439
440
441  /**
442   * {@inheritDoc}
443   */
444  @Override()
445  public byte[] getAssertionValueBytes()
446  {
447    return assertionValue.getValue();
448  }
449
450
451
452  /**
453   * {@inheritDoc}
454   */
455  @Override()
456  public ASN1OctetString getRawAssertionValue()
457  {
458    return assertionValue;
459  }
460
461
462
463  /**
464   * Specifies the assertion value to specify within the target entry.
465   *
466   * @param  assertionValue  The assertion value to specify within the target
467   *                         entry.  It must not be {@code null}.
468   */
469  public void setAssertionValue(final String assertionValue)
470  {
471    ensureNotNull(assertionValue);
472
473    this.assertionValue = new ASN1OctetString(assertionValue);
474  }
475
476
477
478  /**
479   * Specifies the assertion value to specify within the target entry.
480   *
481   * @param  assertionValue  The assertion value to specify within the target
482   *                         entry.  It must not be {@code null}.
483   */
484  public void setAssertionValue(final byte[] assertionValue)
485  {
486    ensureNotNull(assertionValue);
487
488    this.assertionValue = new ASN1OctetString(assertionValue);
489  }
490
491
492
493  /**
494   * Specifies the assertion value to specify within the target entry.
495   *
496   * @param  assertionValue  The assertion value to specify within the target
497   *                         entry.  It must not be {@code null}.
498   */
499  public void setAssertionValue(final ASN1OctetString assertionValue)
500  {
501    this.assertionValue = assertionValue;
502  }
503
504
505
506  /**
507   * {@inheritDoc}
508   */
509  @Override()
510  public byte getProtocolOpType()
511  {
512    return LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST;
513  }
514
515
516
517  /**
518   * {@inheritDoc}
519   */
520  @Override()
521  public void writeTo(final ASN1Buffer buffer)
522  {
523    final ASN1BufferSequence requestSequence =
524         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST);
525    buffer.addOctetString(dn);
526
527    final ASN1BufferSequence avaSequence = buffer.beginSequence();
528    buffer.addOctetString(attributeName);
529    buffer.addElement(assertionValue);
530    avaSequence.end();
531    requestSequence.end();
532  }
533
534
535
536  /**
537   * Encodes the compare request protocol op to an ASN.1 element.
538   *
539   * @return  The ASN.1 element with the encoded compare request protocol op.
540   */
541  @Override()
542  public ASN1Element encodeProtocolOp()
543  {
544    // Create the compare request protocol op.
545    final ASN1Element[] avaElements =
546    {
547      new ASN1OctetString(attributeName),
548      assertionValue
549    };
550
551    final ASN1Element[] protocolOpElements =
552    {
553      new ASN1OctetString(dn),
554      new ASN1Sequence(avaElements)
555    };
556
557    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST,
558                            protocolOpElements);
559  }
560
561
562
563  /**
564   * Sends this delete request to the directory server over the provided
565   * connection and returns the associated response.
566   *
567   * @param  connection  The connection to use to communicate with the directory
568   *                     server.
569   * @param  depth       The current referral depth for this request.  It should
570   *                     always be one for the initial request, and should only
571   *                     be incremented when following referrals.
572   *
573   * @return  An LDAP result object that provides information about the result
574   *          of the delete processing.
575   *
576   * @throws  LDAPException  If a problem occurs while sending the request or
577   *                         reading the response.
578   */
579  @Override()
580  protected CompareResult process(final LDAPConnection connection,
581                                  final int depth)
582            throws LDAPException
583  {
584    if (connection.synchronousMode())
585    {
586      @SuppressWarnings("deprecation")
587      final boolean autoReconnect =
588           connection.getConnectionOptions().autoReconnect();
589      return processSync(connection, depth, autoReconnect);
590    }
591
592    final long requestTime = System.nanoTime();
593    processAsync(connection, null);
594
595    try
596    {
597      // Wait for and process the response.
598      final LDAPResponse response;
599      try
600      {
601        final long responseTimeout = getResponseTimeoutMillis(connection);
602        if (responseTimeout > 0)
603        {
604          response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
605        }
606        else
607        {
608          response = responseQueue.take();
609        }
610      }
611      catch (final InterruptedException ie)
612      {
613        debugException(ie);
614        Thread.currentThread().interrupt();
615        throw new LDAPException(ResultCode.LOCAL_ERROR,
616             ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie);
617      }
618
619      return handleResponse(connection, response,  requestTime, depth, false);
620    }
621    finally
622    {
623      connection.deregisterResponseAcceptor(messageID);
624    }
625  }
626
627
628
629  /**
630   * Sends this compare request to the directory server over the provided
631   * connection and returns the message ID for the request.
632   *
633   * @param  connection      The connection to use to communicate with the
634   *                         directory server.
635   * @param  resultListener  The async result listener that is to be notified
636   *                         when the response is received.  It may be
637   *                         {@code null} only if the result is to be processed
638   *                         by this class.
639   *
640   * @return  The async request ID created for the operation, or {@code null} if
641   *          the provided {@code resultListener} is {@code null} and the
642   *          operation will not actually be processed asynchronously.
643   *
644   * @throws  LDAPException  If a problem occurs while sending the request.
645   */
646  AsyncRequestID processAsync(final LDAPConnection connection,
647                              final AsyncCompareResultListener resultListener)
648                 throws LDAPException
649  {
650    // Create the LDAP message.
651    messageID = connection.nextMessageID();
652    final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
653
654
655    // If the provided async result listener is {@code null}, then we'll use
656    // this class as the message acceptor.  Otherwise, create an async helper
657    // and use it as the message acceptor.
658    final AsyncRequestID asyncRequestID;
659    if (resultListener == null)
660    {
661      asyncRequestID = null;
662      connection.registerResponseAcceptor(messageID, this);
663    }
664    else
665    {
666      final AsyncCompareHelper compareHelper =
667           new AsyncCompareHelper(connection, messageID, resultListener,
668                getIntermediateResponseListener());
669      connection.registerResponseAcceptor(messageID, compareHelper);
670      asyncRequestID = compareHelper.getAsyncRequestID();
671
672      final long timeout = getResponseTimeoutMillis(connection);
673      if (timeout > 0L)
674      {
675        final Timer timer = connection.getTimer();
676        final AsyncTimeoutTimerTask timerTask =
677             new AsyncTimeoutTimerTask(compareHelper);
678        timer.schedule(timerTask, timeout);
679        asyncRequestID.setTimerTask(timerTask);
680      }
681    }
682
683
684    // Send the request to the server.
685    try
686    {
687      debugLDAPRequest(this);
688      connection.getConnectionStatistics().incrementNumCompareRequests();
689      connection.sendMessage(message);
690      return asyncRequestID;
691    }
692    catch (final LDAPException le)
693    {
694      debugException(le);
695
696      connection.deregisterResponseAcceptor(messageID);
697      throw le;
698    }
699  }
700
701
702
703  /**
704   * Processes this compare operation in synchronous mode, in which the same
705   * thread will send the request and read the response.
706   *
707   * @param  connection  The connection to use to communicate with the directory
708   *                     server.
709   * @param  depth       The current referral depth for this request.  It should
710   *                     always be one for the initial request, and should only
711   *                     be incremented when following referrals.
712   * @param  allowRetry   Indicates whether the request may be re-tried on a
713   *                      re-established connection if the initial attempt fails
714   *                      in a way that indicates the connection is no longer
715   *                      valid and autoReconnect is true.
716   *
717   * @return  An LDAP result object that provides information about the result
718   *          of the compare processing.
719   *
720   * @throws  LDAPException  If a problem occurs while sending the request or
721   *                         reading the response.
722   */
723  private CompareResult processSync(final LDAPConnection connection,
724                                    final int depth, final boolean allowRetry)
725          throws LDAPException
726  {
727    // Create the LDAP message.
728    messageID = connection.nextMessageID();
729    final LDAPMessage message =
730         new LDAPMessage(messageID,  this, getControls());
731
732
733    // Set the appropriate timeout on the socket.
734    try
735    {
736      connection.getConnectionInternals(true).getSocket().setSoTimeout(
737           (int) getResponseTimeoutMillis(connection));
738    }
739    catch (final Exception e)
740    {
741      debugException(e);
742    }
743
744
745    // Send the request to the server.
746    final long requestTime = System.nanoTime();
747    debugLDAPRequest(this);
748    connection.getConnectionStatistics().incrementNumCompareRequests();
749    try
750    {
751      connection.sendMessage(message);
752    }
753    catch (final LDAPException le)
754    {
755      debugException(le);
756
757      if (allowRetry)
758      {
759        final CompareResult retryResult = reconnectAndRetry(connection, depth,
760             le.getResultCode());
761        if (retryResult != null)
762        {
763          return retryResult;
764        }
765      }
766
767      throw le;
768    }
769
770    while (true)
771    {
772      final LDAPResponse response;
773      try
774      {
775        response = connection.readResponse(messageID);
776      }
777      catch (final LDAPException le)
778      {
779        debugException(le);
780
781        if ((le.getResultCode() == ResultCode.TIMEOUT) &&
782            connection.getConnectionOptions().abandonOnTimeout())
783        {
784          connection.abandon(messageID);
785        }
786
787        if (allowRetry)
788        {
789          final CompareResult retryResult = reconnectAndRetry(connection, depth,
790               le.getResultCode());
791          if (retryResult != null)
792          {
793            return retryResult;
794          }
795        }
796
797        throw le;
798      }
799
800      if (response instanceof IntermediateResponse)
801      {
802        final IntermediateResponseListener listener =
803             getIntermediateResponseListener();
804        if (listener != null)
805        {
806          listener.intermediateResponseReturned(
807               (IntermediateResponse) response);
808        }
809      }
810      else
811      {
812        return handleResponse(connection, response, requestTime, depth,
813             allowRetry);
814      }
815    }
816  }
817
818
819
820  /**
821   * Performs the necessary processing for handling a response.
822   *
823   * @param  connection   The connection used to read the response.
824   * @param  response     The response to be processed.
825   * @param  requestTime  The time the request was sent to the server.
826   * @param  depth        The current referral depth for this request.  It
827   *                      should always be one for the initial request, and
828   *                      should only be incremented when following referrals.
829   * @param  allowRetry   Indicates whether the request may be re-tried on a
830   *                      re-established connection if the initial attempt fails
831   *                      in a way that indicates the connection is no longer
832   *                      valid and autoReconnect is true.
833   *
834   * @return  The compare result.
835   *
836   * @throws  LDAPException  If a problem occurs.
837   */
838  private CompareResult handleResponse(final LDAPConnection connection,
839                                       final LDAPResponse response,
840                                       final long requestTime, final int depth,
841                                       final boolean allowRetry)
842          throws LDAPException
843  {
844    if (response == null)
845    {
846      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
847      if (connection.getConnectionOptions().abandonOnTimeout())
848      {
849        connection.abandon(messageID);
850      }
851
852      throw new LDAPException(ResultCode.TIMEOUT,
853           ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, messageID, dn,
854                connection.getHostPort()));
855    }
856
857    connection.getConnectionStatistics().incrementNumCompareResponses(
858         System.nanoTime() - requestTime);
859    if (response instanceof ConnectionClosedResponse)
860    {
861      // The connection was closed while waiting for the response.
862      if (allowRetry)
863      {
864        final CompareResult retryResult = reconnectAndRetry(connection, depth,
865             ResultCode.SERVER_DOWN);
866        if (retryResult != null)
867        {
868          return retryResult;
869        }
870      }
871
872      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
873      final String message = ccr.getMessage();
874      if (message == null)
875      {
876        throw new LDAPException(ccr.getResultCode(),
877             ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get(
878                  connection.getHostPort(), toString()));
879      }
880      else
881      {
882        throw new LDAPException(ccr.getResultCode(),
883             ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get(
884                  connection.getHostPort(), toString(), message));
885      }
886    }
887
888    final CompareResult result;
889    if (response instanceof CompareResult)
890    {
891      result = (CompareResult) response;
892    }
893    else
894    {
895      result = new CompareResult((LDAPResult) response);
896    }
897
898    if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
899        followReferrals(connection))
900    {
901      if (depth >= connection.getConnectionOptions().getReferralHopLimit())
902      {
903        return new CompareResult(messageID,
904                                 ResultCode.REFERRAL_LIMIT_EXCEEDED,
905                                 ERR_TOO_MANY_REFERRALS.get(),
906                                 result.getMatchedDN(),
907                                 result.getReferralURLs(),
908                                 result.getResponseControls());
909      }
910
911      return followReferral(result, connection, depth);
912    }
913    else
914    {
915      if (allowRetry)
916      {
917        final CompareResult retryResult = reconnectAndRetry(connection, depth,
918             result.getResultCode());
919        if (retryResult != null)
920        {
921          return retryResult;
922        }
923      }
924
925      return result;
926    }
927  }
928
929
930
931  /**
932   * Attempts to re-establish the connection and retry processing this request
933   * on it.
934   *
935   * @param  connection  The connection to be re-established.
936   * @param  depth       The current referral depth for this request.  It should
937   *                     always be one for the initial request, and should only
938   *                     be incremented when following referrals.
939   * @param  resultCode  The result code for the previous operation attempt.
940   *
941   * @return  The result from re-trying the compare, or {@code null} if it could
942   *          not be re-tried.
943   */
944  private CompareResult reconnectAndRetry(final LDAPConnection connection,
945                                          final int depth,
946                                          final ResultCode resultCode)
947  {
948    try
949    {
950      // We will only want to retry for certain result codes that indicate a
951      // connection problem.
952      switch (resultCode.intValue())
953      {
954        case ResultCode.SERVER_DOWN_INT_VALUE:
955        case ResultCode.DECODING_ERROR_INT_VALUE:
956        case ResultCode.CONNECT_ERROR_INT_VALUE:
957          connection.reconnect();
958          return processSync(connection, depth, false);
959      }
960    }
961    catch (final Exception e)
962    {
963      debugException(e);
964    }
965
966    return null;
967  }
968
969
970
971  /**
972   * Attempts to follow a referral to perform a compare operation in the target
973   * server.
974   *
975   * @param  referralResult  The LDAP result object containing information about
976   *                         the referral to follow.
977   * @param  connection      The connection on which the referral was received.
978   * @param  depth           The number of referrals followed in the course of
979   *                         processing this request.
980   *
981   * @return  The result of attempting to process the compare operation by
982   *          following the referral.
983   *
984   * @throws  LDAPException  If a problem occurs while attempting to establish
985   *                         the referral connection, sending the request, or
986   *                         reading the result.
987   */
988  private CompareResult followReferral(final CompareResult referralResult,
989                                       final LDAPConnection connection,
990                                       final int depth)
991          throws LDAPException
992  {
993    for (final String urlString : referralResult.getReferralURLs())
994    {
995      try
996      {
997        final LDAPURL referralURL = new LDAPURL(urlString);
998        final String host = referralURL.getHost();
999
1000        if (host == null)
1001        {
1002          // We can't handle a referral in which there is no host.
1003          continue;
1004        }
1005
1006        final CompareRequest compareRequest;
1007        if (referralURL.baseDNProvided())
1008        {
1009          compareRequest = new CompareRequest(referralURL.getBaseDN(),
1010                                              attributeName, assertionValue,
1011                                              getControls());
1012        }
1013        else
1014        {
1015          compareRequest = this;
1016        }
1017
1018        final LDAPConnection referralConn = connection.getReferralConnector().
1019             getReferralConnection(referralURL, connection);
1020        try
1021        {
1022          return compareRequest.process(referralConn, depth+1);
1023        }
1024        finally
1025        {
1026          referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1027          referralConn.close();
1028        }
1029      }
1030      catch (final LDAPException le)
1031      {
1032        debugException(le);
1033      }
1034    }
1035
1036    // If we've gotten here, then we could not follow any of the referral URLs,
1037    // so we'll just return the original referral result.
1038    return referralResult;
1039  }
1040
1041
1042
1043  /**
1044   * {@inheritDoc}
1045   */
1046  @InternalUseOnly()
1047  @Override()
1048  public void responseReceived(final LDAPResponse response)
1049         throws LDAPException
1050  {
1051    try
1052    {
1053      responseQueue.put(response);
1054    }
1055    catch (final Exception e)
1056    {
1057      debugException(e);
1058
1059      if (e instanceof InterruptedException)
1060      {
1061        Thread.currentThread().interrupt();
1062      }
1063
1064      throw new LDAPException(ResultCode.LOCAL_ERROR,
1065           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1066    }
1067  }
1068
1069
1070
1071  /**
1072   * {@inheritDoc}
1073   */
1074  @Override()
1075  public int getLastMessageID()
1076  {
1077    return messageID;
1078  }
1079
1080
1081
1082  /**
1083   * {@inheritDoc}
1084   */
1085  @Override()
1086  public OperationType getOperationType()
1087  {
1088    return OperationType.COMPARE;
1089  }
1090
1091
1092
1093  /**
1094   * {@inheritDoc}
1095   */
1096  @Override()
1097  public CompareRequest duplicate()
1098  {
1099    return duplicate(getControls());
1100  }
1101
1102
1103
1104  /**
1105   * {@inheritDoc}
1106   */
1107  @Override()
1108  public CompareRequest duplicate(final Control[] controls)
1109  {
1110    final CompareRequest r = new CompareRequest(dn, attributeName,
1111         assertionValue.getValue(), controls);
1112
1113    if (followReferralsInternal() != null)
1114    {
1115      r.setFollowReferrals(followReferralsInternal());
1116    }
1117
1118    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1119
1120    return r;
1121  }
1122
1123
1124
1125  /**
1126   * {@inheritDoc}
1127   */
1128  @Override()
1129  public void toString(final StringBuilder buffer)
1130  {
1131    buffer.append("CompareRequest(dn='");
1132    buffer.append(dn);
1133    buffer.append("', attr='");
1134    buffer.append(attributeName);
1135    buffer.append("', value='");
1136    buffer.append(assertionValue.stringValue());
1137    buffer.append('\'');
1138
1139    final Control[] controls = getControls();
1140    if (controls.length > 0)
1141    {
1142      buffer.append(", controls={");
1143      for (int i=0; i < controls.length; i++)
1144      {
1145        if (i > 0)
1146        {
1147          buffer.append(", ");
1148        }
1149
1150        buffer.append(controls[i]);
1151      }
1152      buffer.append('}');
1153    }
1154
1155    buffer.append(')');
1156  }
1157
1158
1159
1160  /**
1161   * {@inheritDoc}
1162   */
1163  @Override()
1164  public void toCode(final List<String> lineList, final String requestID,
1165                     final int indentSpaces, final boolean includeProcessing)
1166  {
1167    // Create the arguments for the request variable.
1168    final ArrayList<ToCodeArgHelper> constructorArgs =
1169         new ArrayList<ToCodeArgHelper>(3);
1170    constructorArgs.add(ToCodeArgHelper.createString(dn, "Entry DN"));
1171    constructorArgs.add(ToCodeArgHelper.createString(attributeName,
1172         "Attribute Name"));
1173
1174    // If the attribute is one that we consider sensitive, then we'll use a
1175    // redacted value.  Otherwise, try to use the string value if it's
1176    // printable, or a byte array value if it's not.
1177    if (isSensitiveToCodeAttribute(attributeName))
1178    {
1179      constructorArgs.add(ToCodeArgHelper.createString("---redacted-value",
1180           "Assertion Value (Redacted because " + attributeName + " is " +
1181                "configured as a sensitive attribute)"));
1182    }
1183    else if (isPrintableString(assertionValue.getValue()))
1184    {
1185      constructorArgs.add(ToCodeArgHelper.createString(
1186           assertionValue.stringValue(),
1187           "Assertion Value"));
1188    }
1189    else
1190    {
1191      constructorArgs.add(ToCodeArgHelper.createByteArray(
1192           assertionValue.getValue(), true,
1193           "Assertion Value"));
1194    }
1195
1196    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "CompareRequest",
1197         requestID + "Request", "new CompareRequest", constructorArgs);
1198
1199
1200    // If there are any controls, then add them to the request.
1201    for (final Control c : getControls())
1202    {
1203      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1204           requestID + "Request.addControl",
1205           ToCodeArgHelper.createControl(c, null));
1206    }
1207
1208
1209    // Add lines for processing the request and obtaining the result.
1210    if (includeProcessing)
1211    {
1212      // Generate a string with the appropriate indent.
1213      final StringBuilder buffer = new StringBuilder();
1214      for (int i=0; i < indentSpaces; i++)
1215      {
1216        buffer.append(' ');
1217      }
1218      final String indent = buffer.toString();
1219
1220      lineList.add("");
1221      lineList.add(indent + "try");
1222      lineList.add(indent + '{');
1223      lineList.add(indent + "  CompareResult " + requestID +
1224           "Result = connection.compare(" + requestID + "Request);");
1225      lineList.add(indent + "  // The compare was processed successfully.");
1226      lineList.add(indent + "  boolean compareMatched = " + requestID +
1227           "Result.compareMatched();");
1228      lineList.add(indent + '}');
1229      lineList.add(indent + "catch (LDAPException e)");
1230      lineList.add(indent + '{');
1231      lineList.add(indent + "  // The compare failed.  Maybe the following " +
1232           "will help explain why.");
1233      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1234      lineList.add(indent + "  String message = e.getMessage();");
1235      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1236      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1237      lineList.add(indent + "  Control[] responseControls = " +
1238           "e.getResponseControls();");
1239      lineList.add(indent + '}');
1240    }
1241  }
1242}