001    /*
002     * Copyright 2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 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.util.args;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Iterator;
028    import java.util.List;
029    
030    import com.unboundid.asn1.ASN1OctetString;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.util.Base64;
033    import com.unboundid.util.Debug;
034    import com.unboundid.util.Mutable;
035    import com.unboundid.util.StaticUtils;
036    import com.unboundid.util.ThreadSafety;
037    import com.unboundid.util.ThreadSafetyLevel;
038    
039    import static com.unboundid.util.args.ArgsMessages.*;
040    
041    
042    
043    /**
044     * This class defines an argument that is intended to hold information about one
045     * or more LDAP controls.  Values for this argument must be in one of the
046     * following formats:
047     * <UL>
048     *   <LI>
049     *     oid -- The numeric OID for the control.  The control will not be critical
050     *     and will not have a value.
051     *   </LI>
052     *   <LI>
053     *     oid:criticality -- The numeric OID followed by a colon and the
054     *     criticality.  The control will be critical if the criticality value is
055     *     any of the following:  {@code true}, {@code t}, {@code yes}, {@code y},
056     *     {@code on}, or {@code 1}.  The control will be non-critical if the
057     *     criticality value is any of the following:  {@code false}, {@code f},
058     *     {@code no}, {@code n}, {@code off}, or {@code 0}.  No other criticality
059     *     values will be accepted.
060     *   </LI>
061     *   <LI>
062     *     oid:criticality:value -- The numeric OID followed by a colon and the
063     *     criticality, then a colon and then a string that represents the value for
064     *     the control.
065     *   </LI>
066     *   <LI>
067     *     oid:criticality::base64value -- The numeric OID  followed by a colon and
068     *     the criticality, then two colons and then a string that represents the
069     *     base64-encoded value for the control.
070     *   </LI>
071     * </UL>
072     */
073    @Mutable()
074    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
075    public final class ControlArgument
076           extends Argument
077    {
078      /**
079       * The serial version UID for this serializable class.
080       */
081      private static final long serialVersionUID = -1889200072476038957L;
082    
083    
084    
085      // The argument value validators that have been registered for this argument.
086      private final List<ArgumentValueValidator> validators;
087    
088      // The list of default values for this argument.
089      private final List<Control> defaultValues;
090    
091      // The set of values assigned to this argument.
092      private final List<Control> values;
093    
094    
095    
096      /**
097       * Creates a new control argument with the provided information.  It will not
098       * have a default value.
099       *
100       * @param  shortIdentifier   The short identifier for this argument.  It may
101       *                           not be {@code null} if the long identifier is
102       *                           {@code null}.
103       * @param  longIdentifier    The long identifier for this argument.  It may
104       *                           not be {@code null} if the short identifier is
105       *                           {@code null}.
106       * @param  isRequired        Indicates whether this argument is required to
107       *                           be provided.
108       * @param  maxOccurrences    The maximum number of times this argument may be
109       *                           provided on the command line.  A value less than
110       *                           or equal to zero indicates that it may be present
111       *                           any number of times.
112       * @param  valuePlaceholder  A placeholder to display in usage information to
113       *                           indicate that a value must be provided.  It may
114       *                           be {@code null} to use a default placeholder that
115       *                           describes the expected syntax for values.
116       * @param  description       A human-readable description for this argument.
117       *                           It must not be {@code null}.
118       *
119       * @throws  ArgumentException  If there is a problem with the definition of
120       *                             this argument.
121       */
122      public ControlArgument(final Character shortIdentifier,
123                             final String longIdentifier, final boolean isRequired,
124                             final int maxOccurrences,
125                             final String valuePlaceholder,
126                             final String description)
127             throws ArgumentException
128      {
129        this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
130             valuePlaceholder, description, (List<Control>) null);
131      }
132    
133    
134    
135      /**
136       * Creates a new control argument with the provided information.
137       *
138       * @param  shortIdentifier   The short identifier for this argument.  It may
139       *                           not be {@code null} if the long identifier is
140       *                           {@code null}.
141       * @param  longIdentifier    The long identifier for this argument.  It may
142       *                           not be {@code null} if the short identifier is
143       *                           {@code null}.
144       * @param  isRequired        Indicates whether this argument is required to
145       *                           be provided.
146       * @param  maxOccurrences    The maximum number of times this argument may be
147       *                           provided on the command line.  A value less than
148       *                           or equal to zero indicates that it may be present
149       *                           any number of times.
150       * @param  valuePlaceholder  A placeholder to display in usage information to
151       *                           indicate that a value must be provided.  It may
152       *                           be {@code null} to use a default placeholder that
153       *                           describes the expected syntax for values.
154       * @param  description       A human-readable description for this argument.
155       *                           It must not be {@code null}.
156       * @param  defaultValue      The default value to use for this argument if no
157       *                           values were provided.  It may be {@code null} if
158       *                           there should be no default values.
159       *
160       * @throws  ArgumentException  If there is a problem with the definition of
161       *                             this argument.
162       */
163      public ControlArgument(final Character shortIdentifier,
164                             final String longIdentifier, final boolean isRequired,
165                             final int maxOccurrences,
166                             final String valuePlaceholder,
167                             final String description, final Control defaultValue)
168             throws ArgumentException
169      {
170        this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
171             valuePlaceholder, description,
172             ((defaultValue == null)
173                  ? null :
174                  Collections.singletonList(defaultValue)));
175      }
176    
177    
178    
179      /**
180       * Creates a new control argument with the provided information.
181       *
182       * @param  shortIdentifier   The short identifier for this argument.  It may
183       *                           not be {@code null} if the long identifier is
184       *                           {@code null}.
185       * @param  longIdentifier    The long identifier for this argument.  It may
186       *                           not be {@code null} if the short identifier is
187       *                           {@code null}.
188       * @param  isRequired        Indicates whether this argument is required to
189       *                           be provided.
190       * @param  maxOccurrences    The maximum number of times this argument may be
191       *                           provided on the command line.  A value less than
192       *                           or equal to zero indicates that it may be present
193       *                           any number of times.
194       * @param  valuePlaceholder  A placeholder to display in usage information to
195       *                           indicate that a value must be provided.  It may
196       *                           be {@code null} to use a default placeholder that
197       *                           describes the expected syntax for values.
198       * @param  description       A human-readable description for this argument.
199       *                           It must not be {@code null}.
200       * @param  defaultValues     The set of default values to use for this
201       *                           argument if no values were provided.
202       *
203       * @throws  ArgumentException  If there is a problem with the definition of
204       *                             this argument.
205       */
206      public ControlArgument(final Character shortIdentifier,
207                             final String longIdentifier, final boolean isRequired,
208                             final int maxOccurrences,
209                             final String valuePlaceholder,
210                             final String description,
211                             final List<Control> defaultValues)
212             throws ArgumentException
213      {
214        super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
215             (valuePlaceholder == null)
216                  ? "{oid}[:{criticality}[:{stringValue}|::{base64Value}]]"
217                  : valuePlaceholder,
218             description);
219    
220        if ((defaultValues == null) || defaultValues.isEmpty())
221        {
222          this.defaultValues = null;
223        }
224        else
225        {
226          this.defaultValues = Collections.unmodifiableList(defaultValues);
227        }
228    
229        values = new ArrayList<Control>(5);
230        validators = new ArrayList<ArgumentValueValidator>(5);
231      }
232    
233    
234    
235      /**
236       * Creates a new control argument that is a "clean" copy of the provided
237       * source argument.
238       *
239       * @param  source  The source argument to use for this argument.
240       */
241      private ControlArgument(final ControlArgument source)
242      {
243        super(source);
244    
245        defaultValues = source.defaultValues;
246        validators    = new ArrayList<ArgumentValueValidator>(source.validators);
247        values        = new ArrayList<Control>(5);
248      }
249    
250    
251    
252      /**
253       * Retrieves the list of default values for this argument, which will be used
254       * if no values were provided.
255       *
256       * @return   The list of default values for this argument, or {@code null} if
257       *           there are no default values.
258       */
259      public List<Control> getDefaultValues()
260      {
261        return defaultValues;
262      }
263    
264    
265    
266      /**
267       * Updates this argument to ensure that the provided validator will be invoked
268       * for any values provided to this argument.  This validator will be invoked
269       * after all other validation has been performed for this argument.
270       *
271       * @param  validator  The argument value validator to be invoked.  It must not
272       *                    be {@code null}.
273       */
274      public void addValueValidator(final ArgumentValueValidator validator)
275      {
276        validators.add(validator);
277      }
278    
279    
280    
281      /**
282       * {@inheritDoc}
283       */
284      @Override()
285      protected void addValue(final String valueString)
286                throws ArgumentException
287      {
288        final String oid;
289        boolean isCritical = false;
290        ASN1OctetString value = null;
291    
292        final int firstColonPos = valueString.indexOf(':');
293        if (firstColonPos < 0)
294        {
295          oid = valueString;
296        }
297        else
298        {
299          oid = valueString.substring(0, firstColonPos);
300    
301          final String criticalityStr;
302          final int secondColonPos = valueString.indexOf(':', (firstColonPos+1));
303          if (secondColonPos < 0)
304          {
305            criticalityStr = valueString.substring(firstColonPos+1);
306          }
307          else
308          {
309            criticalityStr = valueString.substring(firstColonPos+1, secondColonPos);
310    
311            final int doubleColonPos = valueString.indexOf("::");
312            if (doubleColonPos == secondColonPos)
313            {
314              try
315              {
316                value = new ASN1OctetString(
317                     Base64.decode(valueString.substring(doubleColonPos+2)));
318              }
319              catch (final Exception e)
320              {
321                Debug.debugException(e);
322                throw new ArgumentException(
323                     ERR_CONTROL_ARG_INVALID_BASE64_VALUE.get(valueString,
324                          getIdentifierString(),
325                          valueString.substring(doubleColonPos+2)),
326                     e);
327              }
328            }
329            else
330            {
331              value = new ASN1OctetString(valueString.substring(secondColonPos+1));
332            }
333          }
334    
335          final String lowerCriticalityStr =
336               StaticUtils.toLowerCase(criticalityStr);
337          if (lowerCriticalityStr.equals("true") ||
338              lowerCriticalityStr.equals("t") ||
339              lowerCriticalityStr.equals("yes") ||
340              lowerCriticalityStr.equals("y") ||
341              lowerCriticalityStr.equals("on") ||
342              lowerCriticalityStr.equals("1"))
343          {
344            isCritical = true;
345          }
346          else if (lowerCriticalityStr.equals("false") ||
347                   lowerCriticalityStr.equals("f") ||
348                   lowerCriticalityStr.equals("no") ||
349                   lowerCriticalityStr.equals("n") ||
350                   lowerCriticalityStr.equals("off") ||
351                   lowerCriticalityStr.equals("0"))
352          {
353            isCritical = false;
354          }
355          else
356          {
357            throw new ArgumentException(ERR_CONTROL_ARG_INVALID_CRITICALITY.get(
358                 valueString, getIdentifierString(), criticalityStr));
359          }
360        }
361    
362        if (! StaticUtils.isNumericOID(oid))
363        {
364          throw new ArgumentException(ERR_CONTROL_ARG_INVALID_OID.get(
365               valueString, getIdentifierString(), oid));
366        }
367    
368        if (values.size() >= getMaxOccurrences())
369        {
370          throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
371                                           getIdentifierString()));
372        }
373    
374        for (final ArgumentValueValidator v : validators)
375        {
376          v.validateArgumentValue(this, valueString);
377        }
378    
379        values.add(new Control(oid, isCritical, value));
380      }
381    
382    
383    
384      /**
385       * Retrieves the value for this argument, or the default value if none was
386       * provided.  If there are multiple values, then the first will be returned.
387       *
388       * @return  The value for this argument, or the default value if none was
389       *          provided, or {@code null} if there is no value and no default
390       *          value.
391       */
392      public Control getValue()
393      {
394        if (values.isEmpty())
395        {
396          if ((defaultValues == null) || defaultValues.isEmpty())
397          {
398            return null;
399          }
400          else
401          {
402            return defaultValues.get(0);
403          }
404        }
405        else
406        {
407          return values.get(0);
408        }
409      }
410    
411    
412    
413      /**
414       * Retrieves the set of values for this argument, or the default values if
415       * none were provided.
416       *
417       * @return  The set of values for this argument, or the default values if none
418       *          were provided.
419       */
420      public List<Control> getValues()
421      {
422        if (values.isEmpty() && (defaultValues != null))
423        {
424          return defaultValues;
425        }
426    
427        return Collections.unmodifiableList(values);
428      }
429    
430    
431    
432      /**
433       * {@inheritDoc}
434       */
435      @Override()
436      protected boolean hasDefaultValue()
437      {
438        return ((defaultValues != null) && (! defaultValues.isEmpty()));
439      }
440    
441    
442    
443      /**
444       * {@inheritDoc}
445       */
446      @Override()
447      public String getDataTypeName()
448      {
449        return INFO_CONTROL_TYPE_NAME.get();
450      }
451    
452    
453    
454      /**
455       * {@inheritDoc}
456       */
457      @Override()
458      public String getValueConstraints()
459      {
460        return INFO_CONTROL_CONSTRAINTS.get();
461      }
462    
463    
464    
465      /**
466       * {@inheritDoc}
467       */
468      @Override()
469      public ControlArgument getCleanCopy()
470      {
471        return new ControlArgument(this);
472      }
473    
474    
475    
476      /**
477       * {@inheritDoc}
478       */
479      @Override()
480      public void toString(final StringBuilder buffer)
481      {
482        buffer.append("ControlArgument(");
483        appendBasicToStringInfo(buffer);
484    
485        if ((defaultValues != null) && (! defaultValues.isEmpty()))
486        {
487          if (defaultValues.size() == 1)
488          {
489            buffer.append(", defaultValue='");
490            buffer.append(defaultValues.get(0).toString());
491          }
492          else
493          {
494            buffer.append(", defaultValues={");
495    
496            final Iterator<Control> iterator = defaultValues.iterator();
497            while (iterator.hasNext())
498            {
499              buffer.append('\'');
500              buffer.append(iterator.next().toString());
501              buffer.append('\'');
502    
503              if (iterator.hasNext())
504              {
505                buffer.append(", ");
506              }
507            }
508    
509            buffer.append('}');
510          }
511        }
512    
513        buffer.append(')');
514      }
515    }