001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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 * Decodes the provided ASN.1 sequence as an LDAP control.
503 *
504 * @param oid The OID for this control. It must not be {@code null}.
505 * @param isCritical Indicates whether this control should be considered
506 * critical.
507 * @param value The value for this control. It may be {@code null} if
508 * there is no value.
509 *
510 * @return The decoded control.
511 *
512 * @throws LDAPException If a problem occurs while attempting to decode the
513 * provided ASN.1 sequence as an LDAP control.
514 */
515 public static Control decode(final String oid, final boolean isCritical,
516 final ASN1OctetString value)
517 throws LDAPException
518 {
519 final DecodeableControl decodeableControl = decodeableControlMap.get(oid);
520 if (decodeableControl == null)
521 {
522 return new Control(oid, isCritical, value);
523 }
524 else
525 {
526 try
527 {
528 return decodeableControl.decodeControl(oid, isCritical, value);
529 }
530 catch (Exception e)
531 {
532 debugException(e);
533 return new Control(oid, isCritical, value);
534 }
535 }
536 }
537
538
539
540 /**
541 * Encodes the provided set of controls to an ASN.1 sequence suitable for
542 * inclusion in an LDAP message.
543 *
544 * @param controls The set of controls to be encoded.
545 *
546 * @return An ASN.1 sequence containing the encoded set of controls.
547 */
548 public static ASN1Sequence encodeControls(final Control[] controls)
549 {
550 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length];
551 for (int i=0; i < controls.length; i++)
552 {
553 controlElements[i] = controls[i].encode();
554 }
555
556 return new ASN1Sequence(CONTROLS_TYPE, controlElements);
557 }
558
559
560
561 /**
562 * Decodes the contents of the provided sequence as a set of controls.
563 *
564 * @param controlSequence The ASN.1 sequence containing the encoded set of
565 * controls.
566 *
567 * @return The decoded set of controls.
568 *
569 * @throws LDAPException If a problem occurs while attempting to decode any
570 * of the controls.
571 */
572 public static Control[] decodeControls(final ASN1Sequence controlSequence)
573 throws LDAPException
574 {
575 final ASN1Element[] controlElements = controlSequence.elements();
576 final Control[] controls = new Control[controlElements.length];
577
578 for (int i=0; i < controlElements.length; i++)
579 {
580 try
581 {
582 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i]));
583 }
584 catch (ASN1Exception ae)
585 {
586 debugException(ae);
587 throw new LDAPException(ResultCode.DECODING_ERROR,
588 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get(
589 getExceptionMessage(ae)),
590 ae);
591 }
592 }
593
594 return controls;
595 }
596
597
598
599 /**
600 * Registers the provided class to be used in an attempt to decode controls
601 * with the specified OID.
602 *
603 * @param oid The response control OID for which the provided
604 * class will be registered.
605 * @param controlInstance The control instance that should be used to decode
606 * controls with the provided OID.
607 */
608 public static void registerDecodeableControl(final String oid,
609 final DecodeableControl controlInstance)
610 {
611 decodeableControlMap.put(oid, controlInstance);
612 }
613
614
615
616 /**
617 * Deregisters the decodeable control class associated with the provided OID.
618 *
619 * @param oid The response control OID for which to deregister the
620 * decodeable control class.
621 */
622 public static void deregisterDecodeableControl(final String oid)
623 {
624 decodeableControlMap.remove(oid);
625 }
626
627
628
629 /**
630 * Retrieves a hash code for this control.
631 *
632 * @return A hash code for this control.
633 */
634 @Override()
635 public final int hashCode()
636 {
637 int hashCode = oid.hashCode();
638
639 if (isCritical)
640 {
641 hashCode++;
642 }
643
644 if (value != null)
645 {
646 hashCode += value.hashCode();
647 }
648
649 return hashCode;
650 }
651
652
653
654 /**
655 * Indicates whether the provided object may be considered equal to this
656 * control.
657 *
658 * @param o The object for which to make the determination.
659 *
660 * @return {@code true} if the provided object may be considered equal to
661 * this control, or {@code false} if not.
662 */
663 @Override()
664 public final boolean equals(final Object o)
665 {
666 if (o == null)
667 {
668 return false;
669 }
670
671 if (o == this)
672 {
673 return true;
674 }
675
676 if (! (o instanceof Control))
677 {
678 return false;
679 }
680
681 final Control c = (Control) o;
682 if (! oid.equals(c.oid))
683 {
684 return false;
685 }
686
687 if (isCritical != c.isCritical)
688 {
689 return false;
690 }
691
692 if (value == null)
693 {
694 if (c.value != null)
695 {
696 return false;
697 }
698 }
699 else
700 {
701 if (c.value == null)
702 {
703 return false;
704 }
705
706 if (! value.equals(c.value))
707 {
708 return false;
709 }
710 }
711
712
713 return true;
714 }
715
716
717
718 /**
719 * Retrieves the user-friendly name for this control, if available. If no
720 * user-friendly name has been defined, then the OID will be returned.
721 *
722 * @return The user-friendly name for this control, or the OID if no
723 * user-friendly name is available.
724 */
725 public String getControlName()
726 {
727 // By default, we will return the OID. Subclasses should override this to
728 // provide the user-friendly name.
729 return oid;
730 }
731
732
733
734 /**
735 * Retrieves a string representation of this LDAP control.
736 *
737 * @return A string representation of this LDAP control.
738 */
739 @Override()
740 public String toString()
741 {
742 final StringBuilder buffer = new StringBuilder();
743 toString(buffer);
744 return buffer.toString();
745 }
746
747
748
749 /**
750 * Appends a string representation of this LDAP control to the provided
751 * buffer.
752 *
753 * @param buffer The buffer to which to append the string representation of
754 * this buffer.
755 */
756 public void toString(final StringBuilder buffer)
757 {
758 buffer.append("Control(oid=");
759 buffer.append(oid);
760 buffer.append(", isCritical=");
761 buffer.append(isCritical);
762 buffer.append(", value=");
763
764 if (value == null)
765 {
766 buffer.append("{null}");
767 }
768 else
769 {
770 buffer.append("{byte[");
771 buffer.append(value.getValue().length);
772 buffer.append("]}");
773 }
774
775 buffer.append(')');
776 }
777 }