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    }