001    /*
002     * Copyright 2015-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015-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.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       * be required, will be allowed any number of times, will use a default
099       * placeholder, and will not have a default value.
100       *
101       * @param  shortIdentifier   The short identifier for this argument.  It may
102       *                           not be {@code null} if the long identifier is
103       *                           {@code null}.
104       * @param  longIdentifier    The long identifier for this argument.  It may
105       *                           not be {@code null} if the short identifier is
106       *                           {@code null}.
107       * @param  description       A human-readable description for this argument.
108       *                           It must not be {@code null}.
109       *
110       * @throws  ArgumentException  If there is a problem with the definition of
111       *                             this argument.
112       */
113      public ControlArgument(final Character shortIdentifier,
114                             final String longIdentifier, final String description)
115             throws ArgumentException
116      {
117        this(shortIdentifier, longIdentifier, false, 0, null, description);
118      }
119    
120    
121    
122      /**
123       * Creates a new control argument with the provided information.  It will not
124       * have a default value.
125       *
126       * @param  shortIdentifier   The short identifier for this argument.  It may
127       *                           not be {@code null} if the long identifier is
128       *                           {@code null}.
129       * @param  longIdentifier    The long identifier for this argument.  It may
130       *                           not be {@code null} if the short identifier is
131       *                           {@code null}.
132       * @param  isRequired        Indicates whether this argument is required to
133       *                           be provided.
134       * @param  maxOccurrences    The maximum number of times this argument may be
135       *                           provided on the command line.  A value less than
136       *                           or equal to zero indicates that it may be present
137       *                           any number of times.
138       * @param  valuePlaceholder  A placeholder to display in usage information to
139       *                           indicate that a value must be provided.  It may
140       *                           be {@code null} to use a default placeholder that
141       *                           describes the expected syntax for values.
142       * @param  description       A human-readable description for this argument.
143       *                           It must not be {@code null}.
144       *
145       * @throws  ArgumentException  If there is a problem with the definition of
146       *                             this argument.
147       */
148      public ControlArgument(final Character shortIdentifier,
149                             final String longIdentifier, final boolean isRequired,
150                             final int maxOccurrences,
151                             final String valuePlaceholder,
152                             final String description)
153             throws ArgumentException
154      {
155        this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
156             valuePlaceholder, description, (List<Control>) null);
157      }
158    
159    
160    
161      /**
162       * Creates a new control argument with the provided information.
163       *
164       * @param  shortIdentifier   The short identifier for this argument.  It may
165       *                           not be {@code null} if the long identifier is
166       *                           {@code null}.
167       * @param  longIdentifier    The long identifier for this argument.  It may
168       *                           not be {@code null} if the short identifier is
169       *                           {@code null}.
170       * @param  isRequired        Indicates whether this argument is required to
171       *                           be provided.
172       * @param  maxOccurrences    The maximum number of times this argument may be
173       *                           provided on the command line.  A value less than
174       *                           or equal to zero indicates that it may be present
175       *                           any number of times.
176       * @param  valuePlaceholder  A placeholder to display in usage information to
177       *                           indicate that a value must be provided.  It may
178       *                           be {@code null} to use a default placeholder that
179       *                           describes the expected syntax for values.
180       * @param  description       A human-readable description for this argument.
181       *                           It must not be {@code null}.
182       * @param  defaultValue      The default value to use for this argument if no
183       *                           values were provided.  It may be {@code null} if
184       *                           there should be no default values.
185       *
186       * @throws  ArgumentException  If there is a problem with the definition of
187       *                             this argument.
188       */
189      public ControlArgument(final Character shortIdentifier,
190                             final String longIdentifier, final boolean isRequired,
191                             final int maxOccurrences,
192                             final String valuePlaceholder,
193                             final String description, final Control defaultValue)
194             throws ArgumentException
195      {
196        this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
197             valuePlaceholder, description,
198             ((defaultValue == null)
199                  ? null :
200                  Collections.singletonList(defaultValue)));
201      }
202    
203    
204    
205      /**
206       * Creates a new control argument with the provided information.
207       *
208       * @param  shortIdentifier   The short identifier for this argument.  It may
209       *                           not be {@code null} if the long identifier is
210       *                           {@code null}.
211       * @param  longIdentifier    The long identifier for this argument.  It may
212       *                           not be {@code null} if the short identifier is
213       *                           {@code null}.
214       * @param  isRequired        Indicates whether this argument is required to
215       *                           be provided.
216       * @param  maxOccurrences    The maximum number of times this argument may be
217       *                           provided on the command line.  A value less than
218       *                           or equal to zero indicates that it may be present
219       *                           any number of times.
220       * @param  valuePlaceholder  A placeholder to display in usage information to
221       *                           indicate that a value must be provided.  It may
222       *                           be {@code null} to use a default placeholder that
223       *                           describes the expected syntax for values.
224       * @param  description       A human-readable description for this argument.
225       *                           It must not be {@code null}.
226       * @param  defaultValues     The set of default values to use for this
227       *                           argument if no values were provided.
228       *
229       * @throws  ArgumentException  If there is a problem with the definition of
230       *                             this argument.
231       */
232      public ControlArgument(final Character shortIdentifier,
233                             final String longIdentifier, final boolean isRequired,
234                             final int maxOccurrences,
235                             final String valuePlaceholder,
236                             final String description,
237                             final List<Control> defaultValues)
238             throws ArgumentException
239      {
240        super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
241             (valuePlaceholder == null)
242                  ? INFO_PLACEHOLDER_CONTROL.get()
243                  : valuePlaceholder,
244             description);
245    
246        if ((defaultValues == null) || defaultValues.isEmpty())
247        {
248          this.defaultValues = null;
249        }
250        else
251        {
252          this.defaultValues = Collections.unmodifiableList(defaultValues);
253        }
254    
255        values = new ArrayList<Control>(5);
256        validators = new ArrayList<ArgumentValueValidator>(5);
257      }
258    
259    
260    
261      /**
262       * Creates a new control argument that is a "clean" copy of the provided
263       * source argument.
264       *
265       * @param  source  The source argument to use for this argument.
266       */
267      private ControlArgument(final ControlArgument source)
268      {
269        super(source);
270    
271        defaultValues = source.defaultValues;
272        validators    = new ArrayList<ArgumentValueValidator>(source.validators);
273        values        = new ArrayList<Control>(5);
274      }
275    
276    
277    
278      /**
279       * Retrieves the list of default values for this argument, which will be used
280       * if no values were provided.
281       *
282       * @return   The list of default values for this argument, or {@code null} if
283       *           there are no default values.
284       */
285      public List<Control> getDefaultValues()
286      {
287        return defaultValues;
288      }
289    
290    
291    
292      /**
293       * Updates this argument to ensure that the provided validator will be invoked
294       * for any values provided to this argument.  This validator will be invoked
295       * after all other validation has been performed for this argument.
296       *
297       * @param  validator  The argument value validator to be invoked.  It must not
298       *                    be {@code null}.
299       */
300      public void addValueValidator(final ArgumentValueValidator validator)
301      {
302        validators.add(validator);
303      }
304    
305    
306    
307      /**
308       * {@inheritDoc}
309       */
310      @Override()
311      protected void addValue(final String valueString)
312                throws ArgumentException
313      {
314        final String oid;
315        boolean isCritical = false;
316        ASN1OctetString value = null;
317    
318        final int firstColonPos = valueString.indexOf(':');
319        if (firstColonPos < 0)
320        {
321          oid = valueString;
322        }
323        else
324        {
325          oid = valueString.substring(0, firstColonPos);
326    
327          final String criticalityStr;
328          final int secondColonPos = valueString.indexOf(':', (firstColonPos+1));
329          if (secondColonPos < 0)
330          {
331            criticalityStr = valueString.substring(firstColonPos+1);
332          }
333          else
334          {
335            criticalityStr = valueString.substring(firstColonPos+1, secondColonPos);
336    
337            final int doubleColonPos = valueString.indexOf("::");
338            if (doubleColonPos == secondColonPos)
339            {
340              try
341              {
342                value = new ASN1OctetString(
343                     Base64.decode(valueString.substring(doubleColonPos+2)));
344              }
345              catch (final Exception e)
346              {
347                Debug.debugException(e);
348                throw new ArgumentException(
349                     ERR_CONTROL_ARG_INVALID_BASE64_VALUE.get(valueString,
350                          getIdentifierString(),
351                          valueString.substring(doubleColonPos+2)),
352                     e);
353              }
354            }
355            else
356            {
357              value = new ASN1OctetString(valueString.substring(secondColonPos+1));
358            }
359          }
360    
361          final String lowerCriticalityStr =
362               StaticUtils.toLowerCase(criticalityStr);
363          if (lowerCriticalityStr.equals("true") ||
364              lowerCriticalityStr.equals("t") ||
365              lowerCriticalityStr.equals("yes") ||
366              lowerCriticalityStr.equals("y") ||
367              lowerCriticalityStr.equals("on") ||
368              lowerCriticalityStr.equals("1"))
369          {
370            isCritical = true;
371          }
372          else if (lowerCriticalityStr.equals("false") ||
373                   lowerCriticalityStr.equals("f") ||
374                   lowerCriticalityStr.equals("no") ||
375                   lowerCriticalityStr.equals("n") ||
376                   lowerCriticalityStr.equals("off") ||
377                   lowerCriticalityStr.equals("0"))
378          {
379            isCritical = false;
380          }
381          else
382          {
383            throw new ArgumentException(ERR_CONTROL_ARG_INVALID_CRITICALITY.get(
384                 valueString, getIdentifierString(), criticalityStr));
385          }
386        }
387    
388        if (! StaticUtils.isNumericOID(oid))
389        {
390          throw new ArgumentException(ERR_CONTROL_ARG_INVALID_OID.get(
391               valueString, getIdentifierString(), oid));
392        }
393    
394        if (values.size() >= getMaxOccurrences())
395        {
396          throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
397                                           getIdentifierString()));
398        }
399    
400        for (final ArgumentValueValidator v : validators)
401        {
402          v.validateArgumentValue(this, valueString);
403        }
404    
405        values.add(new Control(oid, isCritical, value));
406      }
407    
408    
409    
410      /**
411       * Retrieves the value for this argument, or the default value if none was
412       * provided.  If there are multiple values, then the first will be returned.
413       *
414       * @return  The value for this argument, or the default value if none was
415       *          provided, or {@code null} if there is no value and no default
416       *          value.
417       */
418      public Control getValue()
419      {
420        if (values.isEmpty())
421        {
422          if ((defaultValues == null) || defaultValues.isEmpty())
423          {
424            return null;
425          }
426          else
427          {
428            return defaultValues.get(0);
429          }
430        }
431        else
432        {
433          return values.get(0);
434        }
435      }
436    
437    
438    
439      /**
440       * Retrieves the set of values for this argument, or the default values if
441       * none were provided.
442       *
443       * @return  The set of values for this argument, or the default values if none
444       *          were provided.
445       */
446      public List<Control> getValues()
447      {
448        if (values.isEmpty() && (defaultValues != null))
449        {
450          return defaultValues;
451        }
452    
453        return Collections.unmodifiableList(values);
454      }
455    
456    
457    
458      /**
459       * {@inheritDoc}
460       */
461      @Override()
462      public List<String> getValueStringRepresentations(final boolean useDefault)
463      {
464        final List<Control> controls;
465        if (values.isEmpty())
466        {
467          if (useDefault)
468          {
469            controls = defaultValues;
470          }
471          else
472          {
473            return Collections.emptyList();
474          }
475        }
476        else
477        {
478          controls = values;
479        }
480    
481        if ((controls == null) || controls.isEmpty())
482        {
483          return Collections.emptyList();
484        }
485    
486        final StringBuilder buffer = new StringBuilder();
487        final ArrayList<String> valueStrings =
488             new ArrayList<String>(controls.size());
489        for (final Control c : controls)
490        {
491          buffer.setLength(0);
492          buffer.append(c.getOID());
493          buffer.append(':');
494          buffer.append(c.isCritical());
495    
496          if (c.hasValue())
497          {
498            final byte[] valueBytes = c.getValue().getValue();
499            if (StaticUtils.isPrintableString(valueBytes))
500            {
501              buffer.append(':');
502              buffer.append(c.getValue().stringValue());
503            }
504            else
505            {
506              buffer.append("::");
507              Base64.encode(valueBytes, buffer);
508            }
509          }
510    
511          valueStrings.add(buffer.toString());
512        }
513    
514        return Collections.unmodifiableList(valueStrings);
515      }
516    
517    
518    
519      /**
520       * {@inheritDoc}
521       */
522      @Override()
523      protected boolean hasDefaultValue()
524      {
525        return ((defaultValues != null) && (! defaultValues.isEmpty()));
526      }
527    
528    
529    
530      /**
531       * {@inheritDoc}
532       */
533      @Override()
534      public String getDataTypeName()
535      {
536        return INFO_CONTROL_TYPE_NAME.get();
537      }
538    
539    
540    
541      /**
542       * {@inheritDoc}
543       */
544      @Override()
545      public String getValueConstraints()
546      {
547        return INFO_CONTROL_CONSTRAINTS.get();
548      }
549    
550    
551    
552      /**
553       * {@inheritDoc}
554       */
555      @Override()
556      protected void reset()
557      {
558        super.reset();
559        values.clear();
560      }
561    
562    
563    
564      /**
565       * {@inheritDoc}
566       */
567      @Override()
568      public ControlArgument getCleanCopy()
569      {
570        return new ControlArgument(this);
571      }
572    
573    
574    
575      /**
576       * {@inheritDoc}
577       */
578      @Override()
579      protected void addToCommandLine(final List<String> argStrings)
580      {
581        if (values != null)
582        {
583          final StringBuilder buffer = new StringBuilder();
584          for (final Control c : values)
585          {
586            argStrings.add(getIdentifierString());
587    
588            buffer.setLength(0);
589            buffer.append(c.getOID());
590            buffer.append(':');
591            buffer.append(c.isCritical());
592    
593            if (c.hasValue())
594            {
595              final byte[] valueBytes = c.getValue().getValue();
596              if (StaticUtils.isPrintableString(valueBytes))
597              {
598                buffer.append(':');
599                buffer.append(c.getValue().stringValue());
600              }
601              else
602              {
603                buffer.append("::");
604                Base64.encode(valueBytes, buffer);
605              }
606            }
607    
608            argStrings.add(buffer.toString());
609          }
610        }
611      }
612    
613    
614    
615      /**
616       * {@inheritDoc}
617       */
618      @Override()
619      public void toString(final StringBuilder buffer)
620      {
621        buffer.append("ControlArgument(");
622        appendBasicToStringInfo(buffer);
623    
624        if ((defaultValues != null) && (! defaultValues.isEmpty()))
625        {
626          if (defaultValues.size() == 1)
627          {
628            buffer.append(", defaultValue='");
629            buffer.append(defaultValues.get(0).toString());
630          }
631          else
632          {
633            buffer.append(", defaultValues={");
634    
635            final Iterator<Control> iterator = defaultValues.iterator();
636            while (iterator.hasNext())
637            {
638              buffer.append('\'');
639              buffer.append(iterator.next().toString());
640              buffer.append('\'');
641    
642              if (iterator.hasNext())
643              {
644                buffer.append(", ");
645              }
646            }
647    
648            buffer.append('}');
649          }
650        }
651    
652        buffer.append(')');
653      }
654    }