001 /*
002 * Copyright 2007-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 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.unboundidds.controls.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 open source release, since it doesn't contain
151 // the UnboundID-specific controls. In that case, we'll try the
152 // experimental controls instead.
153 try
154 {
155 final Class<?> experimentalControlHelperClass = Class.forName(
156 "com.unboundid.ldap.sdk.experimental.ControlHelper");
157 final Method method = experimentalControlHelperClass.getMethod(
158 "registerDefaultResponseControls");
159 method.invoke(null);
160 }
161 catch (Exception e2)
162 {
163 // This is expected in the minimal release, since it doesn't contain any
164 // controls.
165 }
166 }
167 }
168
169
170
171 /**
172 * Creates a new empty control instance that is intended to be used only for
173 * decoding controls via the {@code DecodeableControl} interface. All
174 * {@code DecodeableControl} objects must provide a default constructor that
175 * can be used to create an instance suitable for invoking the
176 * {@code decodeControl} method.
177 */
178 protected Control()
179 {
180 oid = null;
181 isCritical = true;
182 value = null;
183 }
184
185
186
187 /**
188 * Creates a new control whose fields are initialized from the contents of the
189 * provided control.
190 *
191 * @param control The control whose information should be used to create
192 * this new control.
193 */
194 protected Control(final Control control)
195 {
196 oid = control.oid;
197 isCritical = control.isCritical;
198 value = control.value;
199 }
200
201
202
203 /**
204 * Creates a new control with the provided OID. It will not be critical, and
205 * it will not have a value.
206 *
207 * @param oid The OID for this control. It must not be {@code null}.
208 */
209 public Control(final String oid)
210 {
211 ensureNotNull(oid);
212
213 this.oid = oid;
214 isCritical = false;
215 value = null;
216 }
217
218
219
220 /**
221 * Creates a new control with the provided OID and criticality. It will not
222 * have a value.
223 *
224 * @param oid The OID for this control. It must not be {@code null}.
225 * @param isCritical Indicates whether this control should be considered
226 * critical.
227 */
228 public Control(final String oid, final boolean isCritical)
229 {
230 ensureNotNull(oid);
231
232 this.oid = oid;
233 this.isCritical = isCritical;
234 value = null;
235 }
236
237
238
239 /**
240 * Creates a new control with the provided information.
241 *
242 * @param oid The OID for this control. It must not be {@code null}.
243 * @param isCritical Indicates whether this control should be considered
244 * critical.
245 * @param value The value for this control. It may be {@code null} if
246 * there is no value.
247 */
248 public Control(final String oid, final boolean isCritical,
249 final ASN1OctetString value)
250 {
251 ensureNotNull(oid);
252
253 this.oid = oid;
254 this.isCritical = isCritical;
255 this.value = value;
256 }
257
258
259
260 /**
261 * Retrieves the OID for this control.
262 *
263 * @return The OID for this control.
264 */
265 public final String getOID()
266 {
267 return oid;
268 }
269
270
271
272 /**
273 * Indicates whether this control should be considered critical.
274 *
275 * @return {@code true} if this control should be considered critical, or
276 * {@code false} if not.
277 */
278 public final boolean isCritical()
279 {
280 return isCritical;
281 }
282
283
284
285 /**
286 * Indicates whether this control has a value.
287 *
288 * @return {@code true} if this control has a value, or {@code false} if not.
289 */
290 public final boolean hasValue()
291 {
292 return (value != null);
293 }
294
295
296
297 /**
298 * Retrieves the encoded value for this control.
299 *
300 * @return The encoded value for this control, or {@code null} if there is no
301 * value.
302 */
303 public final ASN1OctetString getValue()
304 {
305 return value;
306 }
307
308
309
310 /**
311 * Writes an ASN.1-encoded representation of this control to the provided
312 * ASN.1 stream writer.
313 *
314 * @param writer The ASN.1 stream writer to which the encoded representation
315 * should be written.
316 */
317 public final void writeTo(final ASN1Buffer writer)
318 {
319 final ASN1BufferSequence controlSequence = writer.beginSequence();
320 writer.addOctetString(oid);
321
322 if (isCritical)
323 {
324 writer.addBoolean(true);
325 }
326
327 if (value != null)
328 {
329 writer.addOctetString(value.getValue());
330 }
331
332 controlSequence.end();
333 }
334
335
336
337 /**
338 * Encodes this control to an ASN.1 sequence suitable for use in an LDAP
339 * message.
340 *
341 * @return The encoded representation of this control.
342 */
343 public final ASN1Sequence encode()
344 {
345 final ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3);
346 elementList.add(new ASN1OctetString(oid));
347
348 if (isCritical)
349 {
350 elementList.add(new ASN1Boolean(isCritical));
351 }
352
353 if (value != null)
354 {
355 elementList.add(new ASN1OctetString(value.getValue()));
356 }
357
358 return new ASN1Sequence(elementList);
359 }
360
361
362
363 /**
364 * Reads an LDAP control from the provided ASN.1 stream reader.
365 *
366 * @param reader The ASN.1 stream reader from which to read the control.
367 *
368 * @return The decoded control.
369 *
370 * @throws LDAPException If a problem occurs while attempting to read or
371 * parse the control.
372 */
373 public static Control readFrom(final ASN1StreamReader reader)
374 throws LDAPException
375 {
376 try
377 {
378 final ASN1StreamReaderSequence controlSequence = reader.beginSequence();
379 final String oid = reader.readString();
380
381 boolean isCritical = false;
382 ASN1OctetString value = null;
383 while (controlSequence.hasMoreElements())
384 {
385 final byte type = (byte) reader.peek();
386 switch (type)
387 {
388 case UNIVERSAL_BOOLEAN_TYPE:
389 isCritical = reader.readBoolean();
390 break;
391 case UNIVERSAL_OCTET_STRING_TYPE:
392 value = new ASN1OctetString(reader.readBytes());
393 break;
394 default:
395 throw new LDAPException(ResultCode.DECODING_ERROR,
396 ERR_CONTROL_INVALID_TYPE.get(toHex(type)));
397 }
398 }
399
400 return decode(oid, isCritical, value);
401 }
402 catch (LDAPException le)
403 {
404 debugException(le);
405 throw le;
406 }
407 catch (Exception e)
408 {
409 debugException(e);
410 throw new LDAPException(ResultCode.DECODING_ERROR,
411 ERR_CONTROL_CANNOT_DECODE.get(getExceptionMessage(e)), e);
412 }
413 }
414
415
416
417 /**
418 * Decodes the provided ASN.1 sequence as an LDAP control.
419 *
420 * @param controlSequence The ASN.1 sequence to be decoded.
421 *
422 * @return The decoded control.
423 *
424 * @throws LDAPException If a problem occurs while attempting to decode the
425 * provided ASN.1 sequence as an LDAP control.
426 */
427 public static Control decode(final ASN1Sequence controlSequence)
428 throws LDAPException
429 {
430 final ASN1Element[] elements = controlSequence.elements();
431
432 if ((elements.length < 1) || (elements.length > 3))
433 {
434 throw new LDAPException(ResultCode.DECODING_ERROR,
435 ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get(
436 elements.length));
437 }
438
439 final String oid =
440 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
441
442 boolean isCritical = false;
443 ASN1OctetString value = null;
444 if (elements.length == 2)
445 {
446 switch (elements[1].getType())
447 {
448 case UNIVERSAL_BOOLEAN_TYPE:
449 try
450 {
451 isCritical =
452 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
453 }
454 catch (ASN1Exception ae)
455 {
456 debugException(ae);
457 throw new LDAPException(ResultCode.DECODING_ERROR,
458 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)),
459 ae);
460 }
461 break;
462
463 case UNIVERSAL_OCTET_STRING_TYPE:
464 value = ASN1OctetString.decodeAsOctetString(elements[1]);
465 break;
466
467 default:
468 throw new LDAPException(ResultCode.DECODING_ERROR,
469 ERR_CONTROL_INVALID_TYPE.get(
470 toHex(elements[1].getType())));
471 }
472 }
473 else if (elements.length == 3)
474 {
475 try
476 {
477 isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
478 }
479 catch (ASN1Exception ae)
480 {
481 debugException(ae);
482 throw new LDAPException(ResultCode.DECODING_ERROR,
483 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), ae);
484 }
485
486 value = ASN1OctetString.decodeAsOctetString(elements[2]);
487 }
488
489 return decode(oid, isCritical, value);
490 }
491
492
493
494 /**
495 * Decodes the provided ASN.1 sequence as an LDAP control.
496 *
497 * @param oid The OID for this control. It must not be {@code null}.
498 * @param isCritical Indicates whether this control should be considered
499 * critical.
500 * @param value The value for this control. It may be {@code null} if
501 * there is no value.
502 *
503 * @return The decoded control.
504 *
505 * @throws LDAPException If a problem occurs while attempting to decode the
506 * provided ASN.1 sequence as an LDAP control.
507 */
508 public static Control decode(final String oid, final boolean isCritical,
509 final ASN1OctetString value)
510 throws LDAPException
511 {
512 final DecodeableControl decodeableControl = decodeableControlMap.get(oid);
513 if (decodeableControl == null)
514 {
515 return new Control(oid, isCritical, value);
516 }
517 else
518 {
519 try
520 {
521 return decodeableControl.decodeControl(oid, isCritical, value);
522 }
523 catch (Exception e)
524 {
525 debugException(e);
526 return new Control(oid, isCritical, value);
527 }
528 }
529 }
530
531
532
533 /**
534 * Encodes the provided set of controls to an ASN.1 sequence suitable for
535 * inclusion in an LDAP message.
536 *
537 * @param controls The set of controls to be encoded.
538 *
539 * @return An ASN.1 sequence containing the encoded set of controls.
540 */
541 public static ASN1Sequence encodeControls(final Control[] controls)
542 {
543 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length];
544 for (int i=0; i < controls.length; i++)
545 {
546 controlElements[i] = controls[i].encode();
547 }
548
549 return new ASN1Sequence(CONTROLS_TYPE, controlElements);
550 }
551
552
553
554 /**
555 * Decodes the contents of the provided sequence as a set of controls.
556 *
557 * @param controlSequence The ASN.1 sequence containing the encoded set of
558 * controls.
559 *
560 * @return The decoded set of controls.
561 *
562 * @throws LDAPException If a problem occurs while attempting to decode any
563 * of the controls.
564 */
565 public static Control[] decodeControls(final ASN1Sequence controlSequence)
566 throws LDAPException
567 {
568 final ASN1Element[] controlElements = controlSequence.elements();
569 final Control[] controls = new Control[controlElements.length];
570
571 for (int i=0; i < controlElements.length; i++)
572 {
573 try
574 {
575 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i]));
576 }
577 catch (ASN1Exception ae)
578 {
579 debugException(ae);
580 throw new LDAPException(ResultCode.DECODING_ERROR,
581 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get(
582 getExceptionMessage(ae)),
583 ae);
584 }
585 }
586
587 return controls;
588 }
589
590
591
592 /**
593 * Registers the provided class to be used in an attempt to decode controls
594 * with the specified OID.
595 *
596 * @param oid The response control OID for which the provided
597 * class will be registered.
598 * @param controlInstance The control instance that should be used to decode
599 * controls with the provided OID.
600 */
601 public static void registerDecodeableControl(final String oid,
602 final DecodeableControl controlInstance)
603 {
604 decodeableControlMap.put(oid, controlInstance);
605 }
606
607
608
609 /**
610 * Deregisters the decodeable control class associated with the provided OID.
611 *
612 * @param oid The response control OID for which to deregister the
613 * decodeable control class.
614 */
615 public static void deregisterDecodeableControl(final String oid)
616 {
617 decodeableControlMap.remove(oid);
618 }
619
620
621
622 /**
623 * Retrieves a hash code for this control.
624 *
625 * @return A hash code for this control.
626 */
627 @Override()
628 public final int hashCode()
629 {
630 int hashCode = oid.hashCode();
631
632 if (isCritical)
633 {
634 hashCode++;
635 }
636
637 if (value != null)
638 {
639 hashCode += value.hashCode();
640 }
641
642 return hashCode;
643 }
644
645
646
647 /**
648 * Indicates whether the provided object may be considered equal to this
649 * control.
650 *
651 * @param o The object for which to make the determination.
652 *
653 * @return {@code true} if the provided object may be considered equal to
654 * this control, or {@code false} if not.
655 */
656 @Override()
657 public final boolean equals(final Object o)
658 {
659 if (o == null)
660 {
661 return false;
662 }
663
664 if (o == this)
665 {
666 return true;
667 }
668
669 if (! (o instanceof Control))
670 {
671 return false;
672 }
673
674 final Control c = (Control) o;
675 if (! oid.equals(c.oid))
676 {
677 return false;
678 }
679
680 if (isCritical != c.isCritical)
681 {
682 return false;
683 }
684
685 if (value == null)
686 {
687 if (c.value != null)
688 {
689 return false;
690 }
691 }
692 else
693 {
694 if (c.value == null)
695 {
696 return false;
697 }
698
699 if (! value.equals(c.value))
700 {
701 return false;
702 }
703 }
704
705
706 return true;
707 }
708
709
710
711 /**
712 * Retrieves the user-friendly name for this control, if available. If no
713 * user-friendly name has been defined, then the OID will be returned.
714 *
715 * @return The user-friendly name for this control, or the OID if no
716 * user-friendly name is available.
717 */
718 public String getControlName()
719 {
720 // By default, we will return the OID. Subclasses should override this to
721 // provide the user-friendly name.
722 return oid;
723 }
724
725
726
727 /**
728 * Retrieves a string representation of this LDAP control.
729 *
730 * @return A string representation of this LDAP control.
731 */
732 @Override()
733 public String toString()
734 {
735 final StringBuilder buffer = new StringBuilder();
736 toString(buffer);
737 return buffer.toString();
738 }
739
740
741
742 /**
743 * Appends a string representation of this LDAP control to the provided
744 * buffer.
745 *
746 * @param buffer The buffer to which to append the string representation of
747 * this buffer.
748 */
749 public void toString(final StringBuilder buffer)
750 {
751 buffer.append("Control(oid=");
752 buffer.append(oid);
753 buffer.append(", isCritical=");
754 buffer.append(isCritical);
755 buffer.append(", value=");
756
757 if (value == null)
758 {
759 buffer.append("{null}");
760 }
761 else
762 {
763 buffer.append("{byte[");
764 buffer.append(value.getValue().length);
765 buffer.append("]}");
766 }
767
768 buffer.append(')');
769 }
770 }