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    }