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