001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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 public 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 connection.getConnectionOptions().autoReconnect());
576 }
577
578 final long requestTime = System.nanoTime();
579 processAsync(connection, null);
580
581 try
582 {
583 // Wait for and process the response.
584 final LDAPResponse response;
585 try
586 {
587 final long responseTimeout = getResponseTimeoutMillis(connection);
588 if (responseTimeout > 0)
589 {
590 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS);
591 }
592 else
593 {
594 response = responseQueue.take();
595 }
596 }
597 catch (InterruptedException ie)
598 {
599 debugException(ie);
600 throw new LDAPException(ResultCode.LOCAL_ERROR,
601 ERR_COMPARE_INTERRUPTED.get(connection.getHostPort()), ie);
602 }
603
604 return handleResponse(connection, response, requestTime, depth, false);
605 }
606 finally
607 {
608 connection.deregisterResponseAcceptor(messageID);
609 }
610 }
611
612
613
614 /**
615 * Sends this compare request to the directory server over the provided
616 * connection and returns the message ID for the request.
617 *
618 * @param connection The connection to use to communicate with the
619 * directory server.
620 * @param resultListener The async result listener that is to be notified
621 * when the response is received. It may be
622 * {@code null} only if the result is to be processed
623 * by this class.
624 *
625 * @return The async request ID created for the operation, or {@code null} if
626 * the provided {@code resultListener} is {@code null} and the
627 * operation will not actually be processed asynchronously.
628 *
629 * @throws LDAPException If a problem occurs while sending the request.
630 */
631 AsyncRequestID processAsync(final LDAPConnection connection,
632 final AsyncCompareResultListener resultListener)
633 throws LDAPException
634 {
635 // Create the LDAP message.
636 messageID = connection.nextMessageID();
637 final LDAPMessage message = new LDAPMessage(messageID, this, getControls());
638
639
640 // If the provided async result listener is {@code null}, then we'll use
641 // this class as the message acceptor. Otherwise, create an async helper
642 // and use it as the message acceptor.
643 final AsyncRequestID asyncRequestID;
644 if (resultListener == null)
645 {
646 asyncRequestID = null;
647 connection.registerResponseAcceptor(messageID, this);
648 }
649 else
650 {
651 final AsyncCompareHelper compareHelper =
652 new AsyncCompareHelper(connection, messageID, resultListener,
653 getIntermediateResponseListener());
654 connection.registerResponseAcceptor(messageID, compareHelper);
655 asyncRequestID = compareHelper.getAsyncRequestID();
656
657 final long timeout = getResponseTimeoutMillis(connection);
658 if (timeout > 0L)
659 {
660 final Timer timer = connection.getTimer();
661 final AsyncTimeoutTimerTask timerTask =
662 new AsyncTimeoutTimerTask(compareHelper);
663 timer.schedule(timerTask, timeout);
664 asyncRequestID.setTimerTask(timerTask);
665 }
666 }
667
668
669 // Send the request to the server.
670 try
671 {
672 debugLDAPRequest(this);
673 connection.getConnectionStatistics().incrementNumCompareRequests();
674 connection.sendMessage(message);
675 return asyncRequestID;
676 }
677 catch (LDAPException le)
678 {
679 debugException(le);
680
681 connection.deregisterResponseAcceptor(messageID);
682 throw le;
683 }
684 }
685
686
687
688 /**
689 * Processes this compare operation in synchronous mode, in which the same
690 * thread will send the request and read the response.
691 *
692 * @param connection The connection to use to communicate with the directory
693 * server.
694 * @param depth The current referral depth for this request. It should
695 * always be one for the initial request, and should only
696 * be incremented when following referrals.
697 * @param allowRetry Indicates whether the request may be re-tried on a
698 * re-established connection if the initial attempt fails
699 * in a way that indicates the connection is no longer
700 * valid and autoReconnect is true.
701 *
702 * @return An LDAP result object that provides information about the result
703 * of the compare processing.
704 *
705 * @throws LDAPException If a problem occurs while sending the request or
706 * reading the response.
707 */
708 private CompareResult processSync(final LDAPConnection connection,
709 final int depth, final boolean allowRetry)
710 throws LDAPException
711 {
712 // Create the LDAP message.
713 messageID = connection.nextMessageID();
714 final LDAPMessage message =
715 new LDAPMessage(messageID, this, getControls());
716
717
718 // Set the appropriate timeout on the socket.
719 try
720 {
721 connection.getConnectionInternals(true).getSocket().setSoTimeout(
722 (int) getResponseTimeoutMillis(connection));
723 }
724 catch (Exception e)
725 {
726 debugException(e);
727 }
728
729
730 // Send the request to the server.
731 final long requestTime = System.nanoTime();
732 debugLDAPRequest(this);
733 connection.getConnectionStatistics().incrementNumCompareRequests();
734 try
735 {
736 connection.sendMessage(message);
737 }
738 catch (final LDAPException le)
739 {
740 debugException(le);
741
742 if (allowRetry)
743 {
744 final CompareResult retryResult = reconnectAndRetry(connection, depth,
745 le.getResultCode());
746 if (retryResult != null)
747 {
748 return retryResult;
749 }
750 }
751
752 throw le;
753 }
754
755 while (true)
756 {
757 final LDAPResponse response;
758 try
759 {
760 response = connection.readResponse(messageID);
761 }
762 catch (final LDAPException le)
763 {
764 debugException(le);
765
766 if ((le.getResultCode() == ResultCode.TIMEOUT) &&
767 connection.getConnectionOptions().abandonOnTimeout())
768 {
769 connection.abandon(messageID);
770 }
771
772 if (allowRetry)
773 {
774 final CompareResult retryResult = reconnectAndRetry(connection, depth,
775 le.getResultCode());
776 if (retryResult != null)
777 {
778 return retryResult;
779 }
780 }
781
782 throw le;
783 }
784
785 if (response instanceof IntermediateResponse)
786 {
787 final IntermediateResponseListener listener =
788 getIntermediateResponseListener();
789 if (listener != null)
790 {
791 listener.intermediateResponseReturned(
792 (IntermediateResponse) response);
793 }
794 }
795 else
796 {
797 return handleResponse(connection, response, requestTime, depth,
798 allowRetry);
799 }
800 }
801 }
802
803
804
805 /**
806 * Performs the necessary processing for handling a response.
807 *
808 * @param connection The connection used to read the response.
809 * @param response The response to be processed.
810 * @param requestTime The time the request was sent to the server.
811 * @param depth The current referral depth for this request. It
812 * should always be one for the initial request, and
813 * should only be incremented when following referrals.
814 * @param allowRetry Indicates whether the request may be re-tried on a
815 * re-established connection if the initial attempt fails
816 * in a way that indicates the connection is no longer
817 * valid and autoReconnect is true.
818 *
819 * @return The compare result.
820 *
821 * @throws LDAPException If a problem occurs.
822 */
823 private CompareResult handleResponse(final LDAPConnection connection,
824 final LDAPResponse response,
825 final long requestTime, final int depth,
826 final boolean allowRetry)
827 throws LDAPException
828 {
829 if (response == null)
830 {
831 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
832 if (connection.getConnectionOptions().abandonOnTimeout())
833 {
834 connection.abandon(messageID);
835 }
836
837 throw new LDAPException(ResultCode.TIMEOUT,
838 ERR_COMPARE_CLIENT_TIMEOUT.get(waitTime, connection.getHostPort()));
839 }
840
841 connection.getConnectionStatistics().incrementNumCompareResponses(
842 System.nanoTime() - requestTime);
843 if (response instanceof ConnectionClosedResponse)
844 {
845 // The connection was closed while waiting for the response.
846 if (allowRetry)
847 {
848 final CompareResult retryResult = reconnectAndRetry(connection, depth,
849 ResultCode.SERVER_DOWN);
850 if (retryResult != null)
851 {
852 return retryResult;
853 }
854 }
855
856 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
857 final String message = ccr.getMessage();
858 if (message == null)
859 {
860 throw new LDAPException(ccr.getResultCode(),
861 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE.get(
862 connection.getHostPort(), toString()));
863 }
864 else
865 {
866 throw new LDAPException(ccr.getResultCode(),
867 ERR_CONN_CLOSED_WAITING_FOR_COMPARE_RESPONSE_WITH_MESSAGE.get(
868 connection.getHostPort(), toString(), message));
869 }
870 }
871
872 final CompareResult result;
873 if (response instanceof CompareResult)
874 {
875 result = (CompareResult) response;
876 }
877 else
878 {
879 result = new CompareResult((LDAPResult) response);
880 }
881
882 if ((result.getResultCode().equals(ResultCode.REFERRAL)) &&
883 followReferrals(connection))
884 {
885 if (depth >= connection.getConnectionOptions().getReferralHopLimit())
886 {
887 return new CompareResult(messageID,
888 ResultCode.REFERRAL_LIMIT_EXCEEDED,
889 ERR_TOO_MANY_REFERRALS.get(),
890 result.getMatchedDN(),
891 result.getReferralURLs(),
892 result.getResponseControls());
893 }
894
895 return followReferral(result, connection, depth);
896 }
897 else
898 {
899 if (allowRetry)
900 {
901 final CompareResult retryResult = reconnectAndRetry(connection, depth,
902 result.getResultCode());
903 if (retryResult != null)
904 {
905 return retryResult;
906 }
907 }
908
909 return result;
910 }
911 }
912
913
914
915 /**
916 * Attempts to re-establish the connection and retry processing this request
917 * on it.
918 *
919 * @param connection The connection to be re-established.
920 * @param depth The current referral depth for this request. It should
921 * always be one for the initial request, and should only
922 * be incremented when following referrals.
923 * @param resultCode The result code for the previous operation attempt.
924 *
925 * @return The result from re-trying the compare, or {@code null} if it could
926 * not be re-tried.
927 */
928 private CompareResult reconnectAndRetry(final LDAPConnection connection,
929 final int depth,
930 final ResultCode resultCode)
931 {
932 try
933 {
934 // We will only want to retry for certain result codes that indicate a
935 // connection problem.
936 switch (resultCode.intValue())
937 {
938 case ResultCode.SERVER_DOWN_INT_VALUE:
939 case ResultCode.DECODING_ERROR_INT_VALUE:
940 case ResultCode.CONNECT_ERROR_INT_VALUE:
941 connection.reconnect();
942 return processSync(connection, depth, false);
943 }
944 }
945 catch (final Exception e)
946 {
947 debugException(e);
948 }
949
950 return null;
951 }
952
953
954
955 /**
956 * Attempts to follow a referral to perform a compare operation in the target
957 * server.
958 *
959 * @param referralResult The LDAP result object containing information about
960 * the referral to follow.
961 * @param connection The connection on which the referral was received.
962 * @param depth The number of referrals followed in the course of
963 * processing this request.
964 *
965 * @return The result of attempting to process the compare operation by
966 * following the referral.
967 *
968 * @throws LDAPException If a problem occurs while attempting to establish
969 * the referral connection, sending the request, or
970 * reading the result.
971 */
972 private CompareResult followReferral(final CompareResult referralResult,
973 final LDAPConnection connection,
974 final int depth)
975 throws LDAPException
976 {
977 for (final String urlString : referralResult.getReferralURLs())
978 {
979 try
980 {
981 final LDAPURL referralURL = new LDAPURL(urlString);
982 final String host = referralURL.getHost();
983
984 if (host == null)
985 {
986 // We can't handle a referral in which there is no host.
987 continue;
988 }
989
990 final CompareRequest compareRequest;
991 if (referralURL.baseDNProvided())
992 {
993 compareRequest = new CompareRequest(referralURL.getBaseDN(),
994 attributeName, assertionValue,
995 getControls());
996 }
997 else
998 {
999 compareRequest = this;
1000 }
1001
1002 final LDAPConnection referralConn = connection.getReferralConnector().
1003 getReferralConnection(referralURL, connection);
1004 try
1005 {
1006 return compareRequest.process(referralConn, depth+1);
1007 }
1008 finally
1009 {
1010 referralConn.setDisconnectInfo(DisconnectType.REFERRAL, null, null);
1011 referralConn.close();
1012 }
1013 }
1014 catch (LDAPException le)
1015 {
1016 debugException(le);
1017 }
1018 }
1019
1020 // If we've gotten here, then we could not follow any of the referral URLs,
1021 // so we'll just return the original referral result.
1022 return referralResult;
1023 }
1024
1025
1026
1027 /**
1028 * {@inheritDoc}
1029 */
1030 @InternalUseOnly()
1031 public void responseReceived(final LDAPResponse response)
1032 throws LDAPException
1033 {
1034 try
1035 {
1036 responseQueue.put(response);
1037 }
1038 catch (Exception e)
1039 {
1040 debugException(e);
1041 throw new LDAPException(ResultCode.LOCAL_ERROR,
1042 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
1043 }
1044 }
1045
1046
1047
1048 /**
1049 * {@inheritDoc}
1050 */
1051 @Override()
1052 public int getLastMessageID()
1053 {
1054 return messageID;
1055 }
1056
1057
1058
1059 /**
1060 * {@inheritDoc}
1061 */
1062 @Override()
1063 public OperationType getOperationType()
1064 {
1065 return OperationType.COMPARE;
1066 }
1067
1068
1069
1070 /**
1071 * {@inheritDoc}
1072 */
1073 public CompareRequest duplicate()
1074 {
1075 return duplicate(getControls());
1076 }
1077
1078
1079
1080 /**
1081 * {@inheritDoc}
1082 */
1083 public CompareRequest duplicate(final Control[] controls)
1084 {
1085 final CompareRequest r = new CompareRequest(dn, attributeName,
1086 assertionValue.getValue(), controls);
1087
1088 if (followReferralsInternal() != null)
1089 {
1090 r.setFollowReferrals(followReferralsInternal());
1091 }
1092
1093 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1094
1095 return r;
1096 }
1097
1098
1099
1100 /**
1101 * {@inheritDoc}
1102 */
1103 @Override()
1104 public void toString(final StringBuilder buffer)
1105 {
1106 buffer.append("CompareRequest(dn='");
1107 buffer.append(dn);
1108 buffer.append("', attr='");
1109 buffer.append(attributeName);
1110 buffer.append("', value='");
1111 buffer.append(assertionValue.stringValue());
1112 buffer.append('\'');
1113
1114 final Control[] controls = getControls();
1115 if (controls.length > 0)
1116 {
1117 buffer.append(", controls={");
1118 for (int i=0; i < controls.length; i++)
1119 {
1120 if (i > 0)
1121 {
1122 buffer.append(", ");
1123 }
1124
1125 buffer.append(controls[i]);
1126 }
1127 buffer.append('}');
1128 }
1129
1130 buffer.append(')');
1131 }
1132 }