001    /*
002     * Copyright 2007-2012 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2012 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.util.Timer;
026    import java.util.concurrent.LinkedBlockingQueue;
027    import java.util.concurrent.TimeUnit;
028    
029    import com.unboundid.asn1.ASN1Buffer;
030    import com.unboundid.asn1.ASN1BufferSequence;
031    import com.unboundid.asn1.ASN1Element;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.protocol.LDAPMessage;
035    import com.unboundid.ldap.protocol.LDAPResponse;
036    import com.unboundid.ldap.protocol.ProtocolOp;
037    import com.unboundid.util.InternalUseOnly;
038    import com.unboundid.util.Mutable;
039    import com.unboundid.util.ThreadSafety;
040    import com.unboundid.util.ThreadSafetyLevel;
041    
042    import static com.unboundid.ldap.sdk.LDAPMessages.*;
043    import static com.unboundid.util.Debug.*;
044    import static com.unboundid.util.StaticUtils.*;
045    import static com.unboundid.util.Validator.*;
046    
047    
048    
049    /**
050     * This class implements the processing necessary to perform an LDAPv3 compare
051     * operation, which may be used to determine whether a specified entry contains
052     * a given attribute value.  Compare requests include the DN of the target
053     * entry, the name of the target attribute, and the value for which to make the
054     * determination.  It may also include a set of controls to send to the server.
055     * <BR><BR>
056     * The assertion value may be specified as either a string or a byte array.  If
057     * it is specified as a byte array, then it may represent either a binary or a
058     * string value.  If a string value is provided as a byte array, then it should
059     * use the UTF-8 encoding for that value.
060     * <BR><BR>
061     * {@code CompareRequest} objects are mutable and therefore can be altered and
062     * re-used for multiple requests.  Note, however, that {@code CompareRequest}
063     * objects are not threadsafe and therefore a single {@code CompareRequest}
064     * object instance should not be used to process multiple requests at the same
065     * time.
066     * <BR><BR>
067     * <H2>Example</H2>
068     * The following example demonstrates the process for performing a compare
069     * operation:
070     * <PRE>
071     *   CompareRequest compareRequest =
072     *        new CompareRequest("dc=example,dc=com", "description", "test");
073     *
074     *   try
075     *   {
076     *     CompareResult compareResult = connection.compare(compareRequest);
077     *
078     *     // The compare operation didn't throw an exception, so we can try to
079     *     // determine whether the compare matched.
080     *     if (compareResult.compareMatched())
081     *     {
082     *       System.out.println("The entry does have a description value of test");
083     *     }
084     *     else
085     *     {
086     *       System.out.println("The entry does not have a description value of " +
087     *                          "test");
088     *     }
089     *   }
090     *   catch (LDAPException le)
091     *   {
092     *     System.err.println("The compare operation failed.");
093     *   }
094     * </PRE>
095     */
096    @Mutable()
097    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
098    public final class CompareRequest
099           extends UpdatableLDAPRequest
100           implements ReadOnlyCompareRequest, ResponseAcceptor, ProtocolOp
101    {
102      /**
103       * The serial version UID for this serializable class.
104       */
105      private static final long serialVersionUID = 6343453776330347024L;
106    
107    
108    
109      // The queue that will be used to receive response messages from the server.
110      private final LinkedBlockingQueue<LDAPResponse> responseQueue =
111           new LinkedBlockingQueue<LDAPResponse>();
112    
113      // The assertion value for this compare request.
114      private ASN1OctetString assertionValue;
115    
116      // The message ID from the last LDAP message sent from this request.
117      private int messageID = -1;
118    
119      // The name of the target attribute.
120      private String attributeName;
121    
122      // The DN of the entry in which the comparison is to be performed.
123      private String dn;
124    
125    
126    
127      /**
128       * Creates a new compare request with the provided information.
129       *
130       * @param  dn              The DN of the entry in which the comparison is to
131       *                         be performed.  It must not be {@code null}.
132       * @param  attributeName   The name of the target attribute for which the
133       *                         comparison is to be performed.  It must not be
134       *                         {@code null}.
135       * @param  assertionValue  The assertion value to verify within the entry.  It
136       *                         must not be {@code null}.
137       */
138      public CompareRequest(final String dn, final String attributeName,
139                            final String assertionValue)
140      {
141        super(null);
142    
143        ensureNotNull(dn, attributeName, assertionValue);
144    
145        this.dn             = dn;
146        this.attributeName  = attributeName;
147        this.assertionValue = new ASN1OctetString(assertionValue);
148      }
149    
150    
151    
152      /**
153       * Creates a new compare request with the provided information.
154       *
155       * @param  dn              The DN of the entry in which the comparison is to
156       *                         be performed.  It must not be {@code null}.
157       * @param  attributeName   The name of the target attribute for which the
158       *                         comparison is to be performed.  It must not be
159       *                         {@code null}.
160       * @param  assertionValue  The assertion value to verify within the entry.  It
161       *                         must not be {@code null}.
162       */
163      public CompareRequest(final String dn, final String attributeName,
164                            final byte[] assertionValue)
165      {
166        super(null);
167    
168        ensureNotNull(dn, attributeName, assertionValue);
169    
170        this.dn             = dn;
171        this.attributeName  = attributeName;
172        this.assertionValue = new ASN1OctetString(assertionValue);
173      }
174    
175    
176    
177      /**
178       * Creates a new compare request with the provided information.
179       *
180       * @param  dn              The DN of the entry in which the comparison is to
181       *                         be performed.  It must not be {@code null}.
182       * @param  attributeName   The name of the target attribute for which the
183       *                         comparison is to be performed.  It must not be
184       *                         {@code null}.
185       * @param  assertionValue  The assertion value to verify within the entry.  It
186       *                         must not be {@code null}.
187       */
188      public CompareRequest(final DN dn, final String attributeName,
189                            final String assertionValue)
190      {
191        super(null);
192    
193        ensureNotNull(dn, attributeName, assertionValue);
194    
195        this.dn             = dn.toString();
196        this.attributeName  = attributeName;
197        this.assertionValue = new ASN1OctetString(assertionValue);
198      }
199    
200    
201    
202      /**
203       * Creates a new compare request with the provided information.
204       *
205       * @param  dn              The DN of the entry in which the comparison is to
206       *                         be performed.  It must not be {@code null}.
207       * @param  attributeName   The name of the target attribute for which the
208       *                         comparison is to be performed.  It must not be
209       *                         {@code null}.
210       * @param  assertionValue  The assertion value to verify within the entry.  It
211       *                         must not be {@code null}.
212       */
213      public CompareRequest(final DN dn, final String attributeName,
214                            final byte[] assertionValue)
215      {
216        super(null);
217    
218        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       * @param  controls        The set of controls for this compare request.
238       */
239      public CompareRequest(final String dn, final String attributeName,
240                            final String assertionValue, final Control[] controls)
241      {
242        super(controls);
243    
244        ensureNotNull(dn, attributeName, assertionValue);
245    
246        this.dn             = dn;
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(final String dn, final String attributeName,
266                            final byte[] assertionValue, final Control[] controls)
267      {
268        super(controls);
269    
270        ensureNotNull(dn, attributeName, assertionValue);
271    
272        this.dn             = dn;
273        this.attributeName  = attributeName;
274        this.assertionValue = new ASN1OctetString(assertionValue);
275      }
276    
277    
278    
279      /**
280       * Creates a new compare request with the provided information.
281       *
282       * @param  dn              The DN of the entry in which the comparison is to
283       *                         be performed.  It must not be {@code null}.
284       * @param  attributeName   The name of the target attribute for which the
285       *                         comparison is to be performed.  It must not be
286       *                         {@code null}.
287       * @param  assertionValue  The assertion value to verify within the entry.  It
288       *                         must not be {@code null}.
289       * @param  controls        The set of controls for this compare request.
290       */
291      public CompareRequest(final DN dn, final String attributeName,
292                            final String assertionValue, final Control[] controls)
293      {
294        super(controls);
295    
296        ensureNotNull(dn, attributeName, assertionValue);
297    
298        this.dn             = dn.toString();
299        this.attributeName  = attributeName;
300        this.assertionValue = new ASN1OctetString(assertionValue);
301      }
302    
303    
304    
305      /**
306       * Creates a new compare request with the provided information.
307       *
308       * @param  dn              The DN of the entry in which the comparison is to
309       *                         be performed.  It must not be {@code null}.
310       * @param  attributeName   The name of the target attribute for which the
311       *                         comparison is to be performed.  It must not be
312       *                         {@code null}.
313       * @param  assertionValue  The assertion value to verify within the entry.  It
314       *                         must not be {@code null}.
315       * @param  controls        The set of controls for this compare request.
316       */
317      public CompareRequest(final DN dn, final String attributeName,
318                            final ASN1OctetString assertionValue,
319                            final Control[] controls)
320      {
321        super(controls);
322    
323        ensureNotNull(dn, attributeName, assertionValue);
324    
325        this.dn             = dn.toString();
326        this.attributeName  = attributeName;
327        this.assertionValue = assertionValue;
328      }
329    
330    
331    
332      /**
333       * Creates a new compare request with the provided information.
334       *
335       * @param  dn              The DN of the entry in which the comparison is to
336       *                         be performed.  It must not be {@code null}.
337       * @param  attributeName   The name of the target attribute for which the
338       *                         comparison is to be performed.  It must not be
339       *                         {@code null}.
340       * @param  assertionValue  The assertion value to verify within the entry.  It
341       *                         must not be {@code null}.
342       * @param  controls        The set of controls for this compare request.
343       */
344      public CompareRequest(final DN dn, final String attributeName,
345                            final byte[] assertionValue, final Control[] controls)
346      {
347        super(controls);
348    
349        ensureNotNull(dn, attributeName, assertionValue);
350    
351        this.dn             = dn.toString();
352        this.attributeName  = attributeName;
353        this.assertionValue = new ASN1OctetString(assertionValue);
354      }
355    
356    
357    
358      /**
359       * {@inheritDoc}
360       */
361      public String getDN()
362      {
363        return dn;
364      }
365    
366    
367    
368      /**
369       * Specifies the DN of the entry in which the comparison is to be performed.
370       *
371       * @param  dn  The DN of the entry in which the comparison is to be performed.
372       *             It must not be {@code null}.
373       */
374      public void setDN(final String dn)
375      {
376        ensureNotNull(dn);
377    
378        this.dn = dn;
379      }
380    
381    
382    
383      /**
384       * Specifies the DN of the entry in which the comparison is to be performed.
385       *
386       * @param  dn  The DN of the entry in which the comparison is to be performed.
387       *             It must not be {@code null}.
388       */
389      public void setDN(final DN dn)
390      {
391        ensureNotNull(dn);
392    
393        this.dn = dn.toString();
394      }
395    
396    
397    
398      /**
399       * {@inheritDoc}
400       */
401      public String getAttributeName()
402      {
403        return attributeName;
404      }
405    
406    
407    
408      /**
409       * Specifies the name of the attribute for which the comparison is to be
410       * performed.
411       *
412       * @param  attributeName  The name of the attribute for which the comparison
413       *                        is to be performed.  It must not be {@code null}.
414       */
415      public void setAttributeName(final String attributeName)
416      {
417        ensureNotNull(attributeName);
418    
419        this.attributeName = attributeName;
420      }
421    
422    
423    
424      /**
425       * {@inheritDoc}
426       */
427      public String getAssertionValue()
428      {
429        return assertionValue.stringValue();
430      }
431    
432    
433    
434      /**
435       * {@inheritDoc}
436       */
437      public byte[] getAssertionValueBytes()
438      {
439        return assertionValue.getValue();
440      }
441    
442    
443    
444      /**
445       * {@inheritDoc}
446       */
447      public ASN1OctetString getRawAssertionValue()
448      {
449        return assertionValue;
450      }
451    
452    
453    
454      /**
455       * Specifies the assertion value to specify within the target entry.
456       *
457       * @param  assertionValue  The assertion value to specify within the target
458       *                         entry.  It must not be {@code null}.
459       */
460      public void setAssertionValue(final String assertionValue)
461      {
462        ensureNotNull(assertionValue);
463    
464        this.assertionValue = new ASN1OctetString(assertionValue);
465      }
466    
467    
468    
469      /**
470       * Specifies the assertion value to specify within the target entry.
471       *
472       * @param  assertionValue  The assertion value to specify within the target
473       *                         entry.  It must not be {@code null}.
474       */
475      public void setAssertionValue(final byte[] assertionValue)
476      {
477        ensureNotNull(assertionValue);
478    
479        this.assertionValue = new ASN1OctetString(assertionValue);
480      }
481    
482    
483    
484      /**
485       * Specifies the assertion value to specify within the target entry.
486       *
487       * @param  assertionValue  The assertion value to specify within the target
488       *                         entry.  It must not be {@code null}.
489       */
490      public void setAssertionValue(final ASN1OctetString assertionValue)
491      {
492        this.assertionValue = assertionValue;
493      }
494    
495    
496    
497      /**
498       * {@inheritDoc}
499       */
500      public byte getProtocolOpType()
501      {
502        return LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST;
503      }
504    
505    
506    
507      /**
508       * {@inheritDoc}
509       */
510      public void writeTo(final ASN1Buffer buffer)
511      {
512        final ASN1BufferSequence requestSequence =
513             buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST);
514        buffer.addOctetString(dn);
515    
516        final ASN1BufferSequence avaSequence = buffer.beginSequence();
517        buffer.addOctetString(attributeName);
518        buffer.addElement(assertionValue);
519        avaSequence.end();
520        requestSequence.end();
521      }
522    
523    
524    
525      /**
526       * Encodes the compare request protocol op to an ASN.1 element.
527       *
528       * @return  The ASN.1 element with the encoded compare request protocol op.
529       */
530      ASN1Element encodeProtocolOp()
531      {
532        // Create the compare request protocol op.
533        final ASN1Element[] avaElements =
534        {
535          new ASN1OctetString(attributeName),
536          assertionValue
537        };
538    
539        final ASN1Element[] protocolOpElements =
540        {
541          new ASN1OctetString(dn),
542          new ASN1Sequence(avaElements)
543        };
544    
545        return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_COMPARE_REQUEST,
546                                protocolOpElements);
547      }
548    
549    
550    
551      /**
552       * Sends this delete request to the directory server over the provided
553       * connection and returns the associated response.
554       *
555       * @param  connection  The connection to use to communicate with the directory
556       *                     server.
557       * @param  depth       The current referral depth for this request.  It should
558       *                     always be one for the initial request, and should only
559       *                     be incremented when following referrals.
560       *
561       * @return  An LDAP result object that provides information about the result
562       *          of the delete processing.
563       *
564       * @throws  LDAPException  If a problem occurs while sending the request or
565       *                         reading the response.
566       */
567      @Override()
568      protected CompareResult process(final LDAPConnection connection,
569                                      final int depth)
570                throws LDAPException
571      {
572        if (connection.synchronousMode())
573        {
574          return processSync(connection, depth);
575        }
576    
577        final long requestTime = System.nanoTime();
578        processAsync(connection, null);
579    
580        try
581        {
582          // Wait for and process the response.
583          final LDAPResponse response;
584          try
585          {
586            final long responseTimeout = getResponseTimeoutMillis(connection);
587            if (responseTimeout > 0)
588            {
589              response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
590            }
591            else
592            {
593              response = responseQueue.take();
594            }
595          }
596          catch (InterruptedException ie)
597          {
598            debugException(ie);
599            throw new LDAPException(ResultCode.LOCAL_ERROR,
600                 ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie);
601          }
602    
603          return handleResponse(connection, response,  requestTime, depth);
604        }
605        finally
606        {
607          connection.deregisterResponseAcceptor(messageID);
608        }
609      }
610    
611    
612    
613      /**
614       * Sends this compare request to the directory server over the provided
615       * connection and returns the message ID for the request.
616       *
617       * @param  connection      The connection to use to communicate with the
618       *                         directory server.
619       * @param  resultListener  The async result listener that is to be notified
620       *                         when the response is received.  It may be
621       *                         {@code null} only if the result is to be processed
622       *                         by this class.
623       *
624       * @return  The async request ID created for the operation, or {@code null} if
625       *          the provided {@code resultListener} is {@code null} and the
626       *          operation will not actually be processed asynchronously.
627       *
628       * @throws  LDAPException  If a problem occurs while sending the request.
629       */
630      AsyncRequestID processAsync(final LDAPConnection connection,
631                                  final AsyncCompareResultListener resultListener)
632                     throws LDAPException
633      {
634        // Create the LDAP message.
635        messageID = connection.nextMessageID();
636        final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
637    
638    
639        // If the provided async result listener is {@code null}, then we'll use
640        // this class as the message acceptor.  Otherwise, create an async helper
641        // and use it as the message acceptor.
642        final AsyncRequestID asyncRequestID;
643        if (resultListener == null)
644        {
645          asyncRequestID = null;
646          connection.registerResponseAcceptor(messageID, this);
647        }
648        else
649        {
650          final AsyncCompareHelper compareHelper =
651               new AsyncCompareHelper(connection, messageID, resultListener,
652                    getIntermediateResponseListener());
653          connection.registerResponseAcceptor(messageID, compareHelper);
654          asyncRequestID = compareHelper.getAsyncRequestID();
655    
656          final long timeout = getResponseTimeoutMillis(connection);
657          if (timeout > 0L)
658          {
659            final Timer timer = connection.getTimer();
660            final AsyncTimeoutTimerTask timerTask =
661                 new AsyncTimeoutTimerTask(compareHelper);
662            timer.schedule(timerTask, timeout);
663            asyncRequestID.setTimerTask(timerTask);
664          }
665        }
666    
667    
668        // Send the request to the server.
669        try
670        {
671          debugLDAPRequest(this);
672          connection.getConnectionStatistics().incrementNumCompareRequests();
673          connection.sendMessage(message);
674          return asyncRequestID;
675        }
676        catch (LDAPException le)
677        {
678          debugException(le);
679    
680          connection.deregisterResponseAcceptor(messageID);
681          throw le;
682        }
683      }
684    
685    
686    
687      /**
688       * Processes this compare operation in synchronous mode, in which the same
689       * thread will send the request and read the response.
690       *
691       * @param  connection  The connection to use to communicate with the directory
692       *                     server.
693       * @param  depth       The current referral depth for this request.  It should
694       *                     always be one for the initial request, and should only
695       *                     be incremented when following referrals.
696       *
697       * @return  An LDAP result object that provides information about the result
698       *          of the compare processing.
699       *
700       * @throws  LDAPException  If a problem occurs while sending the request or
701       *                         reading the response.
702       */
703      private CompareResult processSync(final LDAPConnection connection,
704                                        final int depth)
705              throws LDAPException
706      {
707        // Create the LDAP message.
708        messageID = connection.nextMessageID();
709        final LDAPMessage message =
710             new LDAPMessage(messageID,  this, getControls());
711    
712    
713        // Set the appropriate timeout on the socket.
714        try
715        {
716          connection.getConnectionInternals().getSocket().setSoTimeout(
717               (int) getResponseTimeoutMillis(connection));
718        }
719        catch (Exception e)
720        {
721          debugException(e);
722        }
723    
724    
725        // Send the request to the server.
726        final long requestTime = System.nanoTime();
727        debugLDAPRequest(this);
728        connection.getConnectionStatistics().incrementNumCompareRequests();
729        connection.sendMessage(message);
730    
731        while (true)
732        {
733          final LDAPResponse response;
734          try
735          {
736            response = connection.readResponse(messageID);
737          }
738          catch (final LDAPException le)
739          {
740            debugException(le);
741    
742            if ((le.getResultCode() == ResultCode.TIMEOUT) &&
743                connection.getConnectionOptions().abandonOnTimeout())
744            {
745              connection.abandon(messageID);
746            }
747    
748            throw le;
749          }
750    
751          if (response instanceof IntermediateResponse)
752          {
753            final IntermediateResponseListener listener =
754                 getIntermediateResponseListener();
755            if (listener != null)
756            {
757              listener.intermediateResponseReturned(
758                   (IntermediateResponse) response);
759            }
760          }
761          else
762          {
763            return handleResponse(connection, response, requestTime, depth);
764          }
765        }
766      }
767    
768    
769    
770      /**
771       * Performs the necessary processing for handling a response.
772       *
773       * @param  connection   The connection used to read the response.
774       * @param  response     The response to be processed.
775       * @param  requestTime  The time the request was sent to the server.
776       * @param  depth        The current referral depth for this request.  It
777       *                      should always be one for the initial request, and
778       *                      should only be incremented when following referrals.
779       *
780       * @return  The compare result.
781       *
782       * @throws  LDAPException  If a problem occurs.
783       */
784      private CompareResult handleResponse(final LDAPConnection connection,
785                                           final LDAPResponse response,
786                                           final long requestTime, final int depth)
787              throws LDAPException
788      {
789        if (response == null)
790        {
791          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
792          if (connection.getConnectionOptions().abandonOnTimeout())
793          {
794            connection.abandon(messageID);
795          }
796    
797          throw new LDAPException(ResultCode.TIMEOUT,
798               ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, connection.getHostPort()));
799        }
800    
801        connection.getConnectionStatistics().incrementNumCompareResponses(
802             System.nanoTime() - requestTime);
803        if (response instanceof ConnectionClosedResponse)
804        {
805          // The connection was closed while waiting for the response.
806          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
807          final String message = ccr.getMessage();
808          if (message == null)
809          {
810            throw new LDAPException(ccr.getResultCode(),
811                 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get(
812                      connection.getHostPort(), toString()));
813          }
814          else
815          {
816            throw new LDAPException(ccr.getResultCode(),
817                 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get(
818                      connection.getHostPort(), toString(), message));
819          }
820        }
821    
822        final CompareResult result;
823        if (response instanceof CompareResult)
824        {
825          result = (CompareResult) response;
826        }
827        else
828        {
829          result = new CompareResult((LDAPResult) response);
830        }
831    
832        if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
833            followReferrals(connection))
834        {
835          if (depth >= connection.getConnectionOptions().getReferralHopLimit())
836          {
837            return new CompareResult(messageID,
838                                     ResultCode.REFERRAL_LIMIT_EXCEEDED,
839                                     ERR_TOO_MANY_REFERRALS.get(),
840                                     result.getMatchedDN(),
841                                     result.getReferralURLs(),
842                                     result.getResponseControls());
843          }
844    
845          return followReferral(result, connection, depth);
846        }
847        else
848        {
849          return result;
850        }
851      }
852    
853    
854    
855      /**
856       * Attempts to follow a referral to perform a compare operation in the target
857       * server.
858       *
859       * @param  referralResult  The LDAP result object containing information about
860       *                         the referral to follow.
861       * @param  connection      The connection on which the referral was received.
862       * @param  depth           The number of referrals followed in the course of
863       *                         processing this request.
864       *
865       * @return  The result of attempting to process the compare operation by
866       *          following the referral.
867       *
868       * @throws  LDAPException  If a problem occurs while attempting to establish
869       *                         the referral connection, sending the request, or
870       *                         reading the result.
871       */
872      private CompareResult followReferral(final CompareResult referralResult,
873                                           final LDAPConnection connection,
874                                           final int depth)
875              throws LDAPException
876      {
877        for (final String urlString : referralResult.getReferralURLs())
878        {
879          try
880          {
881            final LDAPURL referralURL = new LDAPURL(urlString);
882            final String host = referralURL.getHost();
883    
884            if (host == null)
885            {
886              // We can't handle a referral in which there is no host.
887              continue;
888            }
889    
890            final CompareRequest compareRequest;
891            if (referralURL.baseDNProvided())
892            {
893              compareRequest = new CompareRequest(referralURL.getBaseDN(),
894                                                  attributeName, assertionValue,
895                                                  getControls());
896            }
897            else
898            {
899              compareRequest = this;
900            }
901    
902            final LDAPConnection referralConn = connection.getReferralConnector().
903                 getReferralConnection(referralURL, connection);
904            try
905            {
906              return compareRequest.process(referralConn, depth+1);
907            }
908            finally
909            {
910              referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
911              referralConn.close();
912            }
913          }
914          catch (LDAPException le)
915          {
916            debugException(le);
917          }
918        }
919    
920        // If we've gotten here, then we could not follow any of the referral URLs,
921        // so we'll just return the original referral result.
922        return referralResult;
923      }
924    
925    
926    
927      /**
928       * {@inheritDoc}
929       */
930      @InternalUseOnly()
931      public void responseReceived(final LDAPResponse response)
932             throws LDAPException
933      {
934        try
935        {
936          responseQueue.put(response);
937        }
938        catch (Exception e)
939        {
940          debugException(e);
941          throw new LDAPException(ResultCode.LOCAL_ERROR,
942               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
943        }
944      }
945    
946    
947    
948      /**
949       * {@inheritDoc}
950       */
951      @Override()
952      public int getLastMessageID()
953      {
954        return messageID;
955      }
956    
957    
958    
959      /**
960       * {@inheritDoc}
961       */
962      @Override()
963      public OperationType getOperationType()
964      {
965        return OperationType.COMPARE;
966      }
967    
968    
969    
970      /**
971       * {@inheritDoc}
972       */
973      public CompareRequest duplicate()
974      {
975        return duplicate(getControls());
976      }
977    
978    
979    
980      /**
981       * {@inheritDoc}
982       */
983      public CompareRequest duplicate(final Control[] controls)
984      {
985        final CompareRequest r = new CompareRequest(dn, attributeName,
986             assertionValue.getValue(), controls);
987    
988        if (followReferralsInternal() != null)
989        {
990          r.setFollowReferrals(followReferralsInternal());
991        }
992    
993        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
994    
995        return r;
996      }
997    
998    
999    
1000      /**
1001       * {@inheritDoc}
1002       */
1003      @Override()
1004      public void toString(final StringBuilder buffer)
1005      {
1006        buffer.append("CompareRequest(dn='");
1007        buffer.append(dn);
1008        buffer.append("', attr='");
1009        buffer.append(attributeName);
1010        buffer.append("', value='");
1011        buffer.append(assertionValue.stringValue());
1012        buffer.append('\'');
1013    
1014        final Control[] controls = getControls();
1015        if (controls.length > 0)
1016        {
1017          buffer.append(", controls={");
1018          for (int i=0; i < controls.length; i++)
1019          {
1020            if (i > 0)
1021            {
1022              buffer.append(", ");
1023            }
1024    
1025            buffer.append(controls[i]);
1026          }
1027          buffer.append('}');
1028        }
1029    
1030        buffer.append(')');
1031      }
1032    }