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