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