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