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 }