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}