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 */
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   * 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}