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 */
021package com.unboundid.util.args;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1OctetString;
031import com.unboundid.ldap.sdk.Control;
032import com.unboundid.util.Base64;
033import com.unboundid.util.Debug;
034import com.unboundid.util.Mutable;
035import com.unboundid.util.StaticUtils;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038
039import 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)
075public 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}