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