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