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