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