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