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