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.io.Serializable;
026 import java.lang.reflect.Method;
027 import java.util.ArrayList;
028 import java.util.concurrent.ConcurrentHashMap;
029
030 import com.unboundid.asn1.ASN1Boolean;
031 import com.unboundid.asn1.ASN1Buffer;
032 import com.unboundid.asn1.ASN1BufferSequence;
033 import com.unboundid.asn1.ASN1Element;
034 import com.unboundid.asn1.ASN1Exception;
035 import com.unboundid.asn1.ASN1OctetString;
036 import com.unboundid.asn1.ASN1Sequence;
037 import com.unboundid.asn1.ASN1StreamReader;
038 import com.unboundid.asn1.ASN1StreamReaderSequence;
039 import com.unboundid.util.Extensible;
040 import com.unboundid.util.NotMutable;
041 import com.unboundid.util.ThreadSafety;
042 import com.unboundid.util.ThreadSafetyLevel;
043
044 import static com.unboundid.asn1.ASN1Constants.*;
045 import static com.unboundid.ldap.sdk.LDAPMessages.*;
046 import static com.unboundid.util.Debug.*;
047 import static com.unboundid.util.StaticUtils.*;
048 import static com.unboundid.util.Validator.*;
049
050
051
052 /**
053 * This class provides a data structure that represents an LDAP control. A
054 * control is an element that may be attached to an LDAP request or response
055 * to provide additional information about the processing that should be (or has
056 * been) performed. This class may be overridden to provide additional
057 * processing for specific types of controls.
058 * <BR><BR>
059 * A control includes the following elements:
060 * <UL>
061 * <LI>An object identifier (OID), which identifies the type of control.</LI>
062 * <LI>A criticality flag, which indicates whether the control should be
063 * considered critical to the processing of the operation. If a control
064 * is marked critical but the server either does not support that control
065 * or it is not appropriate for the associated request, then the server
066 * will reject the request. If a control is not marked critical and the
067 * server either does not support it or it is not appropriate for the
068 * associated request, then the server will simply ignore that
069 * control and process the request as if it were not present.</LI>
070 * <LI>An optional value, which provides additional information for the
071 * control. Some controls do not take values, and the value encoding for
072 * controls which do take values varies based on the type of control.</LI>
073 * </UL>
074 * Controls may be included in a request from the client to the server, as well
075 * as responses from the server to the client (including intermediate response,
076 * search result entry, and search result references, in addition to the final
077 * response message for an operation). When using request controls, they may be
078 * included in the request object at the time it is created, or may be added
079 * after the fact for {@link UpdatableLDAPRequest} objects. When using
080 * response controls, each response control class includes a {@code get} method
081 * that can be used to extract the appropriate control from an appropriate
082 * result (e.g., {@link LDAPResult}, {@link SearchResultEntry}, or
083 * {@link SearchResultReference}).
084 */
085 @Extensible()
086 @NotMutable()
087 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
088 public class Control
089 implements Serializable
090 {
091 /**
092 * The BER type to use for the encoded set of controls in an LDAP message.
093 */
094 private static final byte CONTROLS_TYPE = (byte) 0xA0;
095
096
097
098 // The registered set of decodeable controls, mapped from their OID to the
099 // class implementing the DecodeableControl interface that should be used to
100 // decode controls with that OID.
101 private static final ConcurrentHashMap<String,DecodeableControl>
102 decodeableControlMap = new ConcurrentHashMap<String,DecodeableControl>();
103
104
105
106 /**
107 * The serial version UID for this serializable class.
108 */
109 private static final long serialVersionUID = 4440956109070220054L;
110
111
112
113 // The encoded value for this control, if there is one.
114 private final ASN1OctetString value;
115
116 // Indicates whether this control should be considered critical.
117 private final boolean isCritical;
118
119 // The OID for this control
120 private final String oid;
121
122
123
124 static
125 {
126 try
127 {
128 final Class<?> unboundIDControlHelperClass = Class.forName(
129 "com.unboundid.ldap.sdk.controls.ControlHelper");
130 final Method method = unboundIDControlHelperClass.getMethod(
131 "registerDefaultResponseControls");
132 method.invoke(null);
133 }
134 catch (Exception e)
135 {
136 // This is expected in the minimal release, since it doesn't include any
137 // controls.
138 }
139
140 try
141 {
142 final Class<?> unboundIDControlHelperClass = Class.forName(
143 "com.unboundid.ldap.sdk.experimental.ControlHelper");
144 final Method method = unboundIDControlHelperClass.getMethod(
145 "registerDefaultResponseControls");
146 method.invoke(null);
147 }
148 catch (Exception e)
149 {
150 // This is expected in the minimal release, since it doesn't include any
151 // controls.
152 }
153
154 try
155 {
156 final Class<?> unboundIDControlHelperClass = Class.forName(
157 "com.unboundid.ldap.sdk.unboundidds.controls.ControlHelper");
158 final Method method = unboundIDControlHelperClass.getMethod(
159 "registerDefaultResponseControls");
160 method.invoke(null);
161 }
162 catch (Exception e)
163 {
164 // This is expected in the open source release, since it doesn't contain
165 // the UnboundID-specific controls. In that case, we'll try enable some
166 // additional experimental controls instead.
167 try
168 {
169 final Class<?> experimentalControlHelperClass = Class.forName(
170 "com.unboundid.ldap.sdk.experimental.ControlHelper");
171 final Method method = experimentalControlHelperClass.getMethod(
172 "registerNonCommercialResponseControls");
173 method.invoke(null);
174 }
175 catch (Exception e2)
176 {
177 // This is expected in the minimal release, since it doesn't contain any
178 // controls.
179 }
180 }
181 }
182
183
184
185 /**
186 * Creates a new empty control instance that is intended to be used only for
187 * decoding controls via the {@code DecodeableControl} interface. All
188 * {@code DecodeableControl} objects must provide a default constructor that
189 * can be used to create an instance suitable for invoking the
190 * {@code decodeControl} method.
191 */
192 protected Control()
193 {
194 oid = null;
195 isCritical = true;
196 value = null;
197 }
198
199
200
201 /**
202 * Creates a new control whose fields are initialized from the contents of the
203 * provided control.
204 *
205 * @param control The control whose information should be used to create
206 * this new control.
207 */
208 protected Control(final Control control)
209 {
210 oid = control.oid;
211 isCritical = control.isCritical;
212 value = control.value;
213 }
214
215
216
217 /**
218 * Creates a new control with the provided OID. It will not be critical, and
219 * it will not have a value.
220 *
221 * @param oid The OID for this control. It must not be {@code null}.
222 */
223 public Control(final String oid)
224 {
225 ensureNotNull(oid);
226
227 this.oid = oid;
228 isCritical = false;
229 value = null;
230 }
231
232
233
234 /**
235 * Creates a new control with the provided OID and criticality. It will not
236 * have a value.
237 *
238 * @param oid The OID for this control. It must not be {@code null}.
239 * @param isCritical Indicates whether this control should be considered
240 * critical.
241 */
242 public Control(final String oid, final boolean isCritical)
243 {
244 ensureNotNull(oid);
245
246 this.oid = oid;
247 this.isCritical = isCritical;
248 value = null;
249 }
250
251
252
253 /**
254 * Creates a new control with the provided information.
255 *
256 * @param oid The OID for this control. It must not be {@code null}.
257 * @param isCritical Indicates whether this control should be considered
258 * critical.
259 * @param value The value for this control. It may be {@code null} if
260 * there is no value.
261 */
262 public Control(final String oid, final boolean isCritical,
263 final ASN1OctetString value)
264 {
265 ensureNotNull(oid);
266
267 this.oid = oid;
268 this.isCritical = isCritical;
269 this.value = value;
270 }
271
272
273
274 /**
275 * Retrieves the OID for this control.
276 *
277 * @return The OID for this control.
278 */
279 public final String getOID()
280 {
281 return oid;
282 }
283
284
285
286 /**
287 * Indicates whether this control should be considered critical.
288 *
289 * @return {@code true} if this control should be considered critical, or
290 * {@code false} if not.
291 */
292 public final boolean isCritical()
293 {
294 return isCritical;
295 }
296
297
298
299 /**
300 * Indicates whether this control has a value.
301 *
302 * @return {@code true} if this control has a value, or {@code false} if not.
303 */
304 public final boolean hasValue()
305 {
306 return (value != null);
307 }
308
309
310
311 /**
312 * Retrieves the encoded value for this control.
313 *
314 * @return The encoded value for this control, or {@code null} if there is no
315 * value.
316 */
317 public final ASN1OctetString getValue()
318 {
319 return value;
320 }
321
322
323
324 /**
325 * Writes an ASN.1-encoded representation of this control to the provided
326 * ASN.1 stream writer.
327 *
328 * @param writer The ASN.1 stream writer to which the encoded representation
329 * should be written.
330 */
331 public final void writeTo(final ASN1Buffer writer)
332 {
333 final ASN1BufferSequence controlSequence = writer.beginSequence();
334 writer.addOctetString(oid);
335
336 if (isCritical)
337 {
338 writer.addBoolean(true);
339 }
340
341 if (value != null)
342 {
343 writer.addOctetString(value.getValue());
344 }
345
346 controlSequence.end();
347 }
348
349
350
351 /**
352 * Encodes this control to an ASN.1 sequence suitable for use in an LDAP
353 * message.
354 *
355 * @return The encoded representation of this control.
356 */
357 public final ASN1Sequence encode()
358 {
359 final ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3);
360 elementList.add(new ASN1OctetString(oid));
361
362 if (isCritical)
363 {
364 elementList.add(new ASN1Boolean(isCritical));
365 }
366
367 if (value != null)
368 {
369 elementList.add(new ASN1OctetString(value.getValue()));
370 }
371
372 return new ASN1Sequence(elementList);
373 }
374
375
376
377 /**
378 * Reads an LDAP control from the provided ASN.1 stream reader.
379 *
380 * @param reader The ASN.1 stream reader from which to read the control.
381 *
382 * @return The decoded control.
383 *
384 * @throws LDAPException If a problem occurs while attempting to read or
385 * parse the control.
386 */
387 public static Control readFrom(final ASN1StreamReader reader)
388 throws LDAPException
389 {
390 try
391 {
392 final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
393 final String oid = reader.readString();
394
395 boolean isCritical = false;
396 ASN1OctetString value = null;
397 while (controlSequence.hasMoreElements())
398 {
399 final byte type = (byte) reader.peek();
400 switch (type)
401 {
402 case UNIVERSAL_BOOLEAN_TYPE:
403 isCritical = reader.readBoolean();
404 break;
405 case UNIVERSAL_OCTET_STRING_TYPE:
406 value = new ASN1OctetString(reader.readBytes());
407 break;
408 default:
409 throw new LDAPException(ResultCode.DECODING_ERROR,
410 ERR_CONTROL_INVALID_TYPE.get(toHex(type)));
411 }
412 }
413
414 return decode(oid, isCritical, value);
415 }
416 catch (LDAPException le)
417 {
418 debugException(le);
419 throw le;
420 }
421 catch (Exception e)
422 {
423 debugException(e);
424 throw new LDAPException(ResultCode.DECODING_ERROR,
425 ERR_CONTROL_CANNOT_DECODE.get(getExceptionMessage(e)), e);
426 }
427 }
428
429
430
431 /**
432 * Decodes the provided ASN.1 sequence as an LDAP control.
433 *
434 * @param controlSequence The ASN.1 sequence to be decoded.
435 *
436 * @return The decoded control.
437 *
438 * @throws LDAPException If a problem occurs while attempting to decode the
439 * provided ASN.1 sequence as an LDAP control.
440 */
441 public static Control decode(final ASN1Sequence controlSequence)
442 throws LDAPException
443 {
444 final ASN1Element[] elements = controlSequence.elements();
445
446 if ((elements.length < 1) || (elements.length > 3))
447 {
448 throw new LDAPException(ResultCode.DECODING_ERROR,
449 ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get(
450 elements.length));
451 }
452
453 final String oid =
454 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
455
456 boolean isCritical = false;
457 ASN1OctetString value = null;
458 if (elements.length == 2)
459 {
460 switch (elements[1].getType())
461 {
462 case UNIVERSAL_BOOLEAN_TYPE:
463 try
464 {
465 isCritical =
466 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
467 }
468 catch (ASN1Exception ae)
469 {
470 debugException(ae);
471 throw new LDAPException(ResultCode.DECODING_ERROR,
472 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)),
473 ae);
474 }
475 break;
476
477 case UNIVERSAL_OCTET_STRING_TYPE:
478 value = ASN1OctetString.decodeAsOctetString(elements[1]);
479 break;
480
481 default:
482 throw new LDAPException(ResultCode.DECODING_ERROR,
483 ERR_CONTROL_INVALID_TYPE.get(
484 toHex(elements[1].getType())));
485 }
486 }
487 else if (elements.length == 3)
488 {
489 try
490 {
491 isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
492 }
493 catch (ASN1Exception ae)
494 {
495 debugException(ae);
496 throw new LDAPException(ResultCode.DECODING_ERROR,
497 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), ae);
498 }
499
500 value = ASN1OctetString.decodeAsOctetString(elements[2]);
501 }
502
503 return decode(oid, isCritical, value);
504 }
505
506
507
508 /**
509 * Attempts to create the most appropriate control instance from the provided
510 * information. If a {@link DecodeableControl} instance has been registered
511 * for the specified OID, then this method will attempt to use that instance
512 * to construct a control. If that fails, or if no appropriate
513 * {@code DecodeableControl} is registered, then a generic control will be
514 * returned.
515 *
516 * @param oid The OID for the control. It must not be {@code null}.
517 * @param isCritical Indicates whether the control should be considered
518 * critical.
519 * @param value The value for the control. It may be {@code null} if
520 * there is no value.
521 *
522 * @return The decoded control.
523 *
524 * @throws LDAPException If a problem occurs while attempting to decode the
525 * provided ASN.1 sequence as an LDAP control.
526 */
527 public static Control decode(final String oid, final boolean isCritical,
528 final ASN1OctetString value)
529 throws LDAPException
530 {
531 final DecodeableControl decodeableControl = decodeableControlMap.get(oid);
532 if (decodeableControl == null)
533 {
534 return new Control(oid, isCritical, value);
535 }
536 else
537 {
538 try
539 {
540 return decodeableControl.decodeControl(oid, isCritical, value);
541 }
542 catch (final Exception e)
543 {
544 debugException(e);
545 return new Control(oid, isCritical, value);
546 }
547 }
548 }
549
550
551
552 /**
553 * Encodes the provided set of controls to an ASN.1 sequence suitable for
554 * inclusion in an LDAP message.
555 *
556 * @param controls The set of controls to be encoded.
557 *
558 * @return An ASN.1 sequence containing the encoded set of controls.
559 */
560 public static ASN1Sequence encodeControls(final Control[] controls)
561 {
562 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length];
563 for (int i=0; i < controls.length; i++)
564 {
565 controlElements[i] = controls[i].encode();
566 }
567
568 return new ASN1Sequence(CONTROLS_TYPE, controlElements);
569 }
570
571
572
573 /**
574 * Decodes the contents of the provided sequence as a set of controls.
575 *
576 * @param controlSequence The ASN.1 sequence containing the encoded set of
577 * controls.
578 *
579 * @return The decoded set of controls.
580 *
581 * @throws LDAPException If a problem occurs while attempting to decode any
582 * of the controls.
583 */
584 public static Control[] decodeControls(final ASN1Sequence controlSequence)
585 throws LDAPException
586 {
587 final ASN1Element[] controlElements = controlSequence.elements();
588 final Control[] controls = new Control[controlElements.length];
589
590 for (int i=0; i < controlElements.length; i++)
591 {
592 try
593 {
594 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i]));
595 }
596 catch (ASN1Exception ae)
597 {
598 debugException(ae);
599 throw new LDAPException(ResultCode.DECODING_ERROR,
600 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get(
601 getExceptionMessage(ae)),
602 ae);
603 }
604 }
605
606 return controls;
607 }
608
609
610
611 /**
612 * Registers the provided class to be used in an attempt to decode controls
613 * with the specified OID.
614 *
615 * @param oid The response control OID for which the provided
616 * class will be registered.
617 * @param controlInstance The control instance that should be used to decode
618 * controls with the provided OID.
619 */
620 public static void registerDecodeableControl(final String oid,
621 final DecodeableControl controlInstance)
622 {
623 decodeableControlMap.put(oid, controlInstance);
624 }
625
626
627
628 /**
629 * Deregisters the decodeable control class associated with the provided OID.
630 *
631 * @param oid The response control OID for which to deregister the
632 * decodeable control class.
633 */
634 public static void deregisterDecodeableControl(final String oid)
635 {
636 decodeableControlMap.remove(oid);
637 }
638
639
640
641 /**
642 * Retrieves a hash code for this control.
643 *
644 * @return A hash code for this control.
645 */
646 @Override()
647 public final int hashCode()
648 {
649 int hashCode = oid.hashCode();
650
651 if (isCritical)
652 {
653 hashCode++;
654 }
655
656 if (value != null)
657 {
658 hashCode += value.hashCode();
659 }
660
661 return hashCode;
662 }
663
664
665
666 /**
667 * Indicates whether the provided object may be considered equal to this
668 * control.
669 *
670 * @param o The object for which to make the determination.
671 *
672 * @return {@code true} if the provided object may be considered equal to
673 * this control, or {@code false} if not.
674 */
675 @Override()
676 public final boolean equals(final Object o)
677 {
678 if (o == null)
679 {
680 return false;
681 }
682
683 if (o == this)
684 {
685 return true;
686 }
687
688 if (! (o instanceof Control))
689 {
690 return false;
691 }
692
693 final Control c = (Control) o;
694 if (! oid.equals(c.oid))
695 {
696 return false;
697 }
698
699 if (isCritical != c.isCritical)
700 {
701 return false;
702 }
703
704 if (value == null)
705 {
706 if (c.value != null)
707 {
708 return false;
709 }
710 }
711 else
712 {
713 if (c.value == null)
714 {
715 return false;
716 }
717
718 if (! value.equals(c.value))
719 {
720 return false;
721 }
722 }
723
724
725 return true;
726 }
727
728
729
730 /**
731 * Retrieves the user-friendly name for this control, if available. If no
732 * user-friendly name has been defined, then the OID will be returned.
733 *
734 * @return The user-friendly name for this control, or the OID if no
735 * user-friendly name is available.
736 */
737 public String getControlName()
738 {
739 // By default, we will return the OID. Subclasses should override this to
740 // provide the user-friendly name.
741 return oid;
742 }
743
744
745
746 /**
747 * Retrieves a string representation of this LDAP control.
748 *
749 * @return A string representation of this LDAP control.
750 */
751 @Override()
752 public String toString()
753 {
754 final StringBuilder buffer = new StringBuilder();
755 toString(buffer);
756 return buffer.toString();
757 }
758
759
760
761 /**
762 * Appends a string representation of this LDAP control to the provided
763 * buffer.
764 *
765 * @param buffer The buffer to which to append the string representation of
766 * this buffer.
767 */
768 public void toString(final StringBuilder buffer)
769 {
770 buffer.append("Control(oid=");
771 buffer.append(oid);
772 buffer.append(", isCritical=");
773 buffer.append(isCritical);
774 buffer.append(", value=");
775
776 if (value == null)
777 {
778 buffer.append("{null}");
779 }
780 else
781 {
782 buffer.append("{byte[");
783 buffer.append(value.getValue().length);
784 buffer.append("]}");
785 }
786
787 buffer.append(')');
788 }
789 }