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