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