001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.config.dsconfig; 018 019import static com.forgerock.opendj.cli.ArgumentConstants.*; 020import static com.forgerock.opendj.cli.CliMessages.*; 021import static com.forgerock.opendj.cli.DocGenerationHelper.*; 022import static com.forgerock.opendj.cli.ToolVersionHandler.newToolVersionHandler; 023import static com.forgerock.opendj.cli.Utils.*; 024import static com.forgerock.opendj.dsconfig.DsconfigMessages.*; 025import static com.forgerock.opendj.util.StaticUtils.*; 026import static com.forgerock.opendj.cli.CommonArguments.*; 027 028import static org.forgerock.opendj.config.PropertyOption.*; 029import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.*; 030import static org.forgerock.util.Utils.*; 031 032import java.io.BufferedReader; 033import java.io.BufferedWriter; 034import java.io.File; 035import java.io.FileReader; 036import java.io.FileWriter; 037import java.io.IOException; 038import java.io.InputStreamReader; 039import java.io.OutputStream; 040import java.io.PrintStream; 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.Comparator; 045import java.util.Date; 046import java.util.HashMap; 047import java.util.Iterator; 048import java.util.LinkedList; 049import java.util.List; 050import java.util.Map; 051import java.util.Set; 052import java.util.SortedSet; 053import java.util.TreeMap; 054import java.util.TreeSet; 055import java.util.logging.Level; 056import java.util.logging.LogManager; 057import java.util.logging.Logger; 058 059import org.forgerock.i18n.LocalizableMessage; 060import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 061import org.forgerock.opendj.config.ACIPropertyDefinition; 062import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider; 063import org.forgerock.opendj.config.AbstractManagedObjectDefinition; 064import org.forgerock.opendj.config.AdministratorAction; 065import org.forgerock.opendj.config.AdministratorAction.Type; 066import org.forgerock.opendj.config.AggregationPropertyDefinition; 067import org.forgerock.opendj.config.AliasDefaultBehaviorProvider; 068import org.forgerock.opendj.config.AttributeTypePropertyDefinition; 069import org.forgerock.opendj.config.BooleanPropertyDefinition; 070import org.forgerock.opendj.config.ClassPropertyDefinition; 071import org.forgerock.opendj.config.ConfigurationFramework; 072import org.forgerock.opendj.config.DNPropertyDefinition; 073import org.forgerock.opendj.config.DefaultBehaviorProvider; 074import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider; 075import org.forgerock.opendj.config.DurationPropertyDefinition; 076import org.forgerock.opendj.config.DurationUnit; 077import org.forgerock.opendj.config.EnumPropertyDefinition; 078import org.forgerock.opendj.config.IPAddressMaskPropertyDefinition; 079import org.forgerock.opendj.config.IPAddressPropertyDefinition; 080import org.forgerock.opendj.config.InstantiableRelationDefinition; 081import org.forgerock.opendj.config.IntegerPropertyDefinition; 082import org.forgerock.opendj.config.ManagedObjectOption; 083import org.forgerock.opendj.config.PropertyDefinition; 084import org.forgerock.opendj.config.PropertyDefinitionVisitor; 085import org.forgerock.opendj.config.PropertyOption; 086import org.forgerock.opendj.config.RelationDefinition; 087import org.forgerock.opendj.config.RelationOption; 088import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider; 089import org.forgerock.opendj.config.SetRelationDefinition; 090import org.forgerock.opendj.config.SizePropertyDefinition; 091import org.forgerock.opendj.config.StringPropertyDefinition; 092import org.forgerock.opendj.config.Tag; 093import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider; 094import org.forgerock.opendj.config.client.ManagedObjectDecodingException; 095import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException; 096import org.forgerock.opendj.config.client.OperationRejectedException; 097import org.forgerock.opendj.config.server.ConfigException; 098import org.forgerock.opendj.ldap.DN; 099import org.forgerock.util.Utils; 100 101import com.forgerock.opendj.cli.Argument; 102import com.forgerock.opendj.cli.ArgumentException; 103import com.forgerock.opendj.cli.ArgumentGroup; 104import com.forgerock.opendj.cli.BooleanArgument; 105import com.forgerock.opendj.cli.CliConstants; 106import com.forgerock.opendj.cli.ClientException; 107import com.forgerock.opendj.cli.CommandBuilder; 108import com.forgerock.opendj.cli.ConnectionFactoryProvider; 109import com.forgerock.opendj.cli.ConsoleApplication; 110import com.forgerock.opendj.cli.Menu; 111import com.forgerock.opendj.cli.MenuBuilder; 112import com.forgerock.opendj.cli.MenuCallback; 113import com.forgerock.opendj.cli.MenuResult; 114import com.forgerock.opendj.cli.ReturnCode; 115import com.forgerock.opendj.cli.StringArgument; 116import com.forgerock.opendj.cli.SubCommand; 117import com.forgerock.opendj.cli.SubCommandArgumentParser; 118import com.forgerock.opendj.cli.SubCommandUsageHandler; 119 120/** 121 * This class provides a command-line tool which enables administrators to configure the Directory Server. 122 */ 123public final class DSConfig extends ConsoleApplication { 124 /** 125 * This class provides additional information about subcommands for generated reference documentation. 126 */ 127 private final class DSConfigSubCommandUsageHandler implements SubCommandUsageHandler { 128 /** Marker to open a DocBook XML paragraph. */ 129 private String op = "<para>"; 130 /** Marker to close a DocBook XML paragraph. */ 131 private String cp = "</para>"; 132 133 @Override 134 public String getArgumentAdditionalInfo(SubCommand sc, Argument a, String nameOption) { 135 StringBuilder sb = new StringBuilder(); 136 final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc); 137 if (isHidden(defn)) { 138 return ""; 139 } 140 if (doesHandleProperties(a)) { 141 final LocalizableMessage name = defn.getUserFriendlyName(); 142 sb.append(op).append(REF_DSCFG_ARG_ADDITIONAL_INFO.get(name, name, nameOption)).append(cp).append(EOL); 143 } else { 144 listSubtypes(sb, sc, a, defn); 145 } 146 return sb.toString(); 147 } 148 149 private boolean isHidden(AbstractManagedObjectDefinition<?, ?> defn) { 150 return defn == null || defn.hasOption(ManagedObjectOption.HIDDEN); 151 } 152 153 private void listSubtypes(StringBuilder sb, SubCommand sc, Argument a, 154 AbstractManagedObjectDefinition<?, ?> defn) { 155 if (a.isHidden()) { 156 return; 157 } 158 159 final LocalizableMessage placeholder = a.getValuePlaceholder(); 160 161 Map<String, Object> map = new HashMap<>(); 162 163 final LocalizableMessage name = defn.getUserFriendlyName(); 164 map.put("dependencies", REF_DSCFG_SUBTYPE_DEPENDENCIES.get(name, name, placeholder)); 165 map.put("typesIntro", REF_DSCFG_SUBTYPE_TYPES_INTRO.get(name)); 166 167 List<Map<String, Object>> children = new LinkedList<>(); 168 for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) { 169 if (isHidden(childDefn)) { 170 continue; 171 } 172 Map<String, Object> child = new HashMap<>(); 173 174 child.put("name", childDefn.getName()); 175 child.put("default", REF_DSCFG_CHILD_DEFAULT.get(placeholder, childDefn.getUserFriendlyName())); 176 child.put("enabled", REF_DSCFG_CHILD_ENABLED_BY_DEFAULT.get(propertyExists(childDefn, "enabled"))); 177 178 final String link = getLink(getScriptName() + "-" + sc.getName() + "-" + childDefn.getName()); 179 child.put("link", REF_DSCFG_CHILD_LINK.get(link, defn.getUserFriendlyName())); 180 181 children.add(child); 182 } 183 map.put("children", children); 184 185 applyTemplate(sb, "dscfgListSubtypes.ftl", map); 186 } 187 188 private boolean propertyExists(AbstractManagedObjectDefinition<?, ?> defn, String name) { 189 if (isHidden(defn)) { 190 return false; 191 } 192 try { 193 return defn.getPropertyDefinition(name) != null; 194 } catch (IllegalArgumentException e) { 195 return false; 196 } 197 } 198 199 @Override 200 public String getProperties(SubCommand sc) { 201 final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc); 202 if (isHidden(defn)) { 203 return ""; 204 } 205 206 StringBuilder sb = new StringBuilder(); 207 for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) { 208 if (isHidden(childDefn)) { 209 continue; 210 } 211 final List<PropertyDefinition<?>> props = new ArrayList<>(childDefn.getAllPropertyDefinitions()); 212 Collections.sort(props); 213 Map<String, Object> map = new HashMap<>(); 214 final String propPrefix = getScriptName() + "-" + sc.getName() + "-" + childDefn.getName(); 215 map.put("id", propPrefix); 216 map.put("title", childDefn.getUserFriendlyName()); 217 map.put("intro", REF_DSCFG_PROPS_INTRO.get(defn.getUserFriendlyPluralName(), childDefn.getName())); 218 map.put("list", toVariableList(props, defn)); 219 applyTemplate(sb, "dscfgAppendProps.ftl", map); 220 } 221 return sb.toString(); 222 } 223 224 private AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition(SubCommand sc) { 225 final SubCommandHandler sch = handlers.get(sc); 226 if (sch instanceof HelpSubCommandHandler) { 227 return null; 228 } 229 final RelationDefinition<?, ?> rd = getRelationDefinition(sch); 230 if (isHidden(rd)) { 231 return null; 232 } 233 return rd.getChildDefinition(); 234 } 235 236 private boolean isHidden(RelationDefinition<?, ?> defn) { 237 return defn == null || defn.hasOption(RelationOption.HIDDEN); 238 } 239 240 private List<AbstractManagedObjectDefinition<?, ?>> getLeafChildren( 241 AbstractManagedObjectDefinition<?, ?> defn) { 242 final ArrayList<AbstractManagedObjectDefinition<?, ?>> results = new ArrayList<>(); 243 addIfLeaf(results, defn); 244 Collections.sort(results, new Comparator<AbstractManagedObjectDefinition<?, ?>>() { 245 @Override 246 public int compare(AbstractManagedObjectDefinition<?, ?> o1, AbstractManagedObjectDefinition<?, ?> o2) { 247 return o1.getName().compareTo(o2.getName()); 248 } 249 }); 250 return results; 251 } 252 253 private void addIfLeaf(final Collection<AbstractManagedObjectDefinition<?, ?>> results, 254 final AbstractManagedObjectDefinition<?, ?> defn) { 255 if (defn.getChildren().isEmpty()) { 256 results.add(defn); 257 } else { 258 for (AbstractManagedObjectDefinition<?, ?> child : defn.getChildren()) { 259 addIfLeaf(results, child); 260 } 261 } 262 } 263 264 private RelationDefinition<?, ?> getRelationDefinition(final SubCommandHandler sch) { 265 if (sch instanceof CreateSubCommandHandler) { 266 return ((CreateSubCommandHandler<?, ?>) sch).getRelationDefinition(); 267 } else if (sch instanceof DeleteSubCommandHandler) { 268 return ((DeleteSubCommandHandler) sch).getRelationDefinition(); 269 } else if (sch instanceof ListSubCommandHandler) { 270 return ((ListSubCommandHandler) sch).getRelationDefinition(); 271 } else if (sch instanceof GetPropSubCommandHandler) { 272 return ((GetPropSubCommandHandler) sch).getRelationDefinition(); 273 } else if (sch instanceof SetPropSubCommandHandler) { 274 return ((SetPropSubCommandHandler) sch).getRelationDefinition(); 275 } 276 return null; 277 } 278 279 private String toVariableList(List<PropertyDefinition<?>> props, AbstractManagedObjectDefinition<?, ?> defn) { 280 StringBuilder b = new StringBuilder(); 281 Map<String, Object> map = new HashMap<>(); 282 283 List<Map<String, Object>> properties = new LinkedList<>(); 284 for (PropertyDefinition<?> prop : props) { 285 if (prop.hasOption(HIDDEN)) { 286 continue; 287 } 288 Map<String, Object> property = new HashMap<>(); 289 property.put("term", prop.getName()); 290 property.put("descTitle", REF_TITLE_DESCRIPTION.get()); 291 property.put("description", getDescriptionString(prop)); 292 293 final StringBuilder sb = new StringBuilder(); 294 appendDefaultBehavior(sb, prop); 295 appendAllowedValues(sb, prop); 296 appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_MULTI_VALUED.get().toString(), getYN(prop, MULTI_VALUED)); 297 appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_REQUIRED.get().toString(), getYN(prop, MANDATORY)); 298 appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADMIN_ACTION_REQUIRED.get().toString(), 299 getAdminActionRequired(prop, defn)); 300 appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADVANCED_PROPERTY.get().toString(), 301 getYNAdvanced(prop, ADVANCED)); 302 appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_READ_ONLY.get().toString(), getYN(prop, READ_ONLY)); 303 property.put("list", sb.toString()); 304 305 properties.add(property); 306 } 307 map.put("properties", properties); 308 309 applyTemplate(b, "dscfgVariableList.ftl", map); 310 return b.toString(); 311 } 312 313 private StringBuilder appendVarListEntry(StringBuilder b, String term, Object definition) { 314 Map<String, Object> map = new HashMap<>(); 315 map.put("term", term); 316 map.put("definition", definition); 317 applyTemplate(b, "dscfgVarListEntry.ftl", map); 318 return b; 319 } 320 321 private void appendDefaultBehavior(StringBuilder b, PropertyDefinition<?> prop) { 322 StringBuilder sb = new StringBuilder(); 323 appendDefaultBehaviorString(sb, prop); 324 appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_DEFAULT_VALUE.get().toString(), sb.toString()); 325 } 326 327 private void appendAllowedValues(StringBuilder b, PropertyDefinition<?> prop) { 328 StringBuilder sb = new StringBuilder(); 329 appendSyntax(sb, prop); 330 appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_ALLOWED_VALUES.get().toString(), sb.toString()); 331 } 332 333 private Object getDescriptionString(PropertyDefinition<?> prop) { 334 return ((prop.getSynopsis() != null) ? prop.getSynopsis() + " " : "") 335 + ((prop.getDescription() != null) ? prop.getDescription() : ""); 336 } 337 338 private String getAdminActionRequired(PropertyDefinition<?> prop, AbstractManagedObjectDefinition<?, ?> defn) { 339 final AdministratorAction adminAction = prop.getAdministratorAction(); 340 if (adminAction != null) { 341 final LocalizableMessage synopsis = adminAction.getSynopsis(); 342 final Type actionType = adminAction.getType(); 343 final StringBuilder action = new StringBuilder(); 344 if (actionType == Type.COMPONENT_RESTART) { 345 action.append(op) 346 .append(REF_DSCFG_ADMIN_ACTION_COMPONENT_RESTART.get(defn.getUserFriendlyName())) 347 .append(cp); 348 } else if (actionType == Type.SERVER_RESTART) { 349 action.append(op).append(REF_DSCFG_ADMIN_ACTION_SERVER_RESTART.get()).append(cp); 350 } else if (actionType == Type.NONE) { 351 action.append(op).append(REF_DSCFG_ADMIN_ACTION_NONE.get()).append(cp); 352 } 353 if (synopsis != null) { 354 action.append(op).append(synopsis).append(cp); 355 } 356 return action.toString(); 357 } 358 return op + REF_DSCFG_ADMIN_ACTION_NONE.get() + cp; 359 } 360 361 private String getYN(PropertyDefinition<?> prop, PropertyOption option) { 362 LocalizableMessage msg = prop.hasOption(option) ? REF_DSCFG_PROP_YES.get() : REF_DSCFG_PROP_NO.get(); 363 return op + msg + cp; 364 } 365 366 private String getYNAdvanced(PropertyDefinition<?> prop, PropertyOption option) { 367 LocalizableMessage msg = prop.hasOption(option) 368 ? REF_DSCFG_PROP_YES_ADVANCED.get() : REF_DSCFG_PROP_NO.get(); 369 return op + msg + cp; 370 } 371 372 private void appendDefaultBehaviorString(StringBuilder b, PropertyDefinition<?> prop) { 373 final DefaultBehaviorProvider<?> defaultBehavior = prop.getDefaultBehaviorProvider(); 374 if (defaultBehavior instanceof UndefinedDefaultBehaviorProvider) { 375 b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL); 376 } else if (defaultBehavior instanceof DefinedDefaultBehaviorProvider) { 377 DefinedDefaultBehaviorProvider<?> behavior = (DefinedDefaultBehaviorProvider<?>) defaultBehavior; 378 final Collection<String> defaultValues = behavior.getDefaultValues(); 379 if (defaultValues.isEmpty()) { 380 b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL); 381 } else if (defaultValues.size() == 1) { 382 b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(defaultValues.iterator().next())) 383 .append(cp).append(EOL); 384 } else { 385 final Iterator<String> it = defaultValues.iterator(); 386 b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp); 387 for (; it.hasNext();) { 388 b.append(EOL).append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp); 389 } 390 b.append(EOL); 391 } 392 } else if (defaultBehavior instanceof AliasDefaultBehaviorProvider) { 393 AliasDefaultBehaviorProvider<?> behavior = (AliasDefaultBehaviorProvider<?>) defaultBehavior; 394 b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(behavior.getSynopsis())).append(cp).append(EOL); 395 } else if (defaultBehavior instanceof RelativeInheritedDefaultBehaviorProvider) { 396 final RelativeInheritedDefaultBehaviorProvider<?> behavior = 397 (RelativeInheritedDefaultBehaviorProvider<?>) defaultBehavior; 398 appendDefaultBehaviorString(b, 399 behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName())); 400 } else if (defaultBehavior instanceof AbsoluteInheritedDefaultBehaviorProvider) { 401 final AbsoluteInheritedDefaultBehaviorProvider<?> behavior = 402 (AbsoluteInheritedDefaultBehaviorProvider<?>) defaultBehavior; 403 appendDefaultBehaviorString(b, 404 behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName())); 405 } 406 } 407 408 private void appendSyntax(final StringBuilder b, PropertyDefinition<?> prop) { 409 // Create a visitor for performing syntax specific processing. 410 PropertyDefinitionVisitor<String, Void> visitor = new PropertyDefinitionVisitor<String, Void>() { 411 412 @Override 413 public String visitACI(ACIPropertyDefinition prop, Void p) { 414 b.append(op).append(REF_DSCFG_ACI_SYNTAX_REL_URL.get()).append(cp).append(EOL); 415 return null; 416 } 417 418 @Override 419 public String visitAggregation(AggregationPropertyDefinition prop, Void p) { 420 b.append(op); 421 final RelationDefinition<?, ?> rel = prop.getRelationDefinition(); 422 if (isHidden(rel)) { 423 return null; 424 } 425 final String relFriendlyName = rel.getUserFriendlyName().toString(); 426 b.append(REF_DSCFG_AGGREGATION.get(relFriendlyName)).append(". "); 427 final LocalizableMessage synopsis = prop.getSourceConstraintSynopsis(); 428 if (synopsis != null) { 429 b.append(synopsis); 430 } 431 b.append(cp).append(EOL); 432 return null; 433 } 434 435 @Override 436 public String visitAttributeType(AttributeTypePropertyDefinition prop, Void p) { 437 b.append(op).append(REF_DSCFG_ANY_ATTRIBUTE.get()).append(".").append(cp).append(EOL); 438 return null; 439 } 440 441 @Override 442 public String visitBoolean(BooleanPropertyDefinition prop, Void p) { 443 b.append(op).append("true").append(cp).append(EOL); 444 b.append(op).append("false").append(cp).append(EOL); 445 return null; 446 } 447 448 @Override 449 public String visitClass(ClassPropertyDefinition prop, Void p) { 450 b.append(op).append(REF_DSCFG_JAVA_PLUGIN.get()).append(" ") 451 .append(Utils.joinAsString(EOL, prop.getInstanceOfInterface())).append(cp).append(EOL); 452 return null; 453 } 454 455 @Override 456 public String visitDN(DNPropertyDefinition prop, Void p) { 457 b.append(op).append(REF_DSCFG_VALID_DN.get()); 458 final DN baseDN = prop.getBaseDN(); 459 if (baseDN != null) { 460 b.append(": ").append(baseDN); 461 } else { 462 b.append("."); 463 } 464 b.append(cp).append(EOL); 465 return null; 466 } 467 468 @Override 469 public String visitDuration(DurationPropertyDefinition prop, Void p) { 470 b.append(REF_DSCFG_DURATION_SYNTAX_REL_URL.get()).append(EOL); 471 b.append(op); 472 if (prop.isAllowUnlimited()) { 473 b.append(REF_DSCFG_ALLOW_UNLIMITED.get()).append(" "); 474 } 475 if (prop.getMaximumUnit() != null) { 476 final String maxUnitName = prop.getMaximumUnit().getLongName(); 477 b.append(REF_DSCFG_DURATION_MAX_UNIT.get(maxUnitName)).append("."); 478 } 479 final DurationUnit baseUnit = prop.getBaseUnit(); 480 final long lowerLimit = valueOf(baseUnit, prop.getLowerLimit()); 481 final String unitName = baseUnit.getLongName(); 482 b.append(REF_DSCFG_DURATION_LOWER_LIMIT.get(lowerLimit, unitName)).append("."); 483 if (prop.getUpperLimit() != null) { 484 final long upperLimit = valueOf(baseUnit, prop.getUpperLimit()); 485 b.append(REF_DSCFG_DURATION_UPPER_LIMIT.get(upperLimit, unitName)).append("."); 486 } 487 b.append(cp).append(EOL); 488 return null; 489 } 490 491 private long valueOf(final DurationUnit baseUnit, long upperLimit) { 492 return Double.valueOf(baseUnit.fromMilliSeconds(upperLimit)).longValue(); 493 } 494 495 @Override 496 public String visitEnum(EnumPropertyDefinition prop, Void p) { 497 b.append("<variablelist>").append(EOL); 498 final Class<?> en = prop.getEnumClass(); 499 final Object[] constants = en.getEnumConstants(); 500 for (Object enumConstant : constants) { 501 final LocalizableMessage valueSynopsis = prop.getValueSynopsis((Enum) enumConstant); 502 appendVarListEntry(b, enumConstant.toString(), op + valueSynopsis + cp); 503 } 504 b.append("</variablelist>").append(EOL); 505 return null; 506 } 507 508 @Override 509 public String visitInteger(IntegerPropertyDefinition prop, Void p) { 510 b.append(op).append(REF_DSCFG_INT.get()).append(". ") 511 .append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append("."); 512 if (prop.getUpperLimit() != null) { 513 b.append(" ").append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append("."); 514 } 515 if (prop.isAllowUnlimited()) { 516 b.append(" ").append(REF_DSCFG_ALLOW_UNLIMITED.get()); 517 } 518 if (prop.getUnitSynopsis() != null) { 519 b.append(" ").append(REF_DSCFG_INT_UNIT.get(prop.getUnitSynopsis())).append("."); 520 } 521 b.append(cp).append(EOL); 522 return null; 523 } 524 525 @Override 526 public String visitIPAddress(IPAddressPropertyDefinition prop, Void p) { 527 b.append(op).append(REF_DSCFG_IP_ADDRESS.get()).append(cp).append(EOL); 528 return null; 529 } 530 531 @Override 532 public String visitIPAddressMask(IPAddressMaskPropertyDefinition prop, Void p) { 533 b.append(op).append(REF_DSCFG_IP_ADDRESS_MASK.get()).append(cp).append(EOL); 534 return null; 535 } 536 537 @Override 538 public String visitSize(SizePropertyDefinition prop, Void p) { 539 b.append(op); 540 if (prop.getLowerLimit() != 0) { 541 b.append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append("."); 542 } 543 if (prop.getUpperLimit() != null) { 544 b.append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append("."); 545 } 546 if (prop.isAllowUnlimited()) { 547 b.append(REF_DSCFG_ALLOW_UNLIMITED.get()); 548 } 549 b.append(cp).append(EOL); 550 return null; 551 } 552 553 @Override 554 public String visitString(StringPropertyDefinition prop, Void p) { 555 b.append(op); 556 if (prop.getPatternSynopsis() != null) { 557 b.append(prop.getPatternSynopsis()); 558 } else { 559 b.append(REF_DSCFG_STRING.get()); 560 } 561 b.append(cp).append(EOL); 562 return null; 563 } 564 565 @Override 566 public String visitUnknown(PropertyDefinition prop, Void p) { 567 b.append(op).append(REF_DSCFG_UNKNOWN.get()).append(cp).append(EOL); 568 return null; 569 } 570 }; 571 572 // Invoke the visitor against the property definition. 573 prop.accept(visitor, null); 574 } 575 576 private String getLink(String target) { 577 return " <xref linkend=\"" + target + "\" />"; 578 } 579 } 580 581 /** The name of this tool. */ 582 static final String DSCONFIGTOOLNAME = "dsconfig"; 583 584 /** The name of a command-line script used to launch an administrative tool. */ 585 static final String PROPERTY_SCRIPT_NAME = "org.opends.server.scriptName"; 586 587 /** A menu call-back which runs a sub-command interactively. */ 588 private class SubCommandHandlerMenuCallback implements MenuCallback<Integer> { 589 /** The sub-command handler. */ 590 private final SubCommandHandler handler; 591 592 /** 593 * Creates a new sub-command handler call-back. 594 * 595 * @param handler 596 * The sub-command handler. 597 */ 598 public SubCommandHandlerMenuCallback(SubCommandHandler handler) { 599 this.handler = handler; 600 } 601 602 @Override 603 public MenuResult<Integer> invoke(ConsoleApplication app) throws ClientException { 604 try { 605 final MenuResult<Integer> result = handler.run(app, factory); 606 if (result.isQuit()) { 607 return result; 608 } 609 if (result.isSuccess() && isInteractive() && handler.isCommandBuilderUseful()) { 610 printCommandBuilder(getCommandBuilder(handler)); 611 } 612 // Success or cancel. 613 app.println(); 614 app.pressReturnToContinue(); 615 return MenuResult.again(); 616 } catch (ArgumentException e) { 617 app.errPrintln(e.getMessageObject()); 618 return MenuResult.success(1); 619 } catch (ClientException e) { 620 app.errPrintln(e.getMessageObject()); 621 return MenuResult.success(e.getReturnCode()); 622 } 623 } 624 } 625 626 /** The interactive mode sub-menu implementation. */ 627 private class SubMenuCallback implements MenuCallback<Integer> { 628 /** The menu. */ 629 private final Menu<Integer> menu; 630 631 /** 632 * Creates a new sub-menu implementation. 633 * 634 * @param app 635 * The console application. 636 * @param rd 637 * The relation definition. 638 * @param ch 639 * The optional create sub-command. 640 * @param dh 641 * The optional delete sub-command. 642 * @param lh 643 * The optional list sub-command. 644 * @param sh 645 * The option set-prop sub-command. 646 */ 647 public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd, CreateSubCommandHandler<?, ?> ch, 648 DeleteSubCommandHandler dh, ListSubCommandHandler lh, SetPropSubCommandHandler sh) { 649 LocalizableMessage userFriendlyName = rd.getUserFriendlyName(); 650 651 LocalizableMessage userFriendlyPluralName = null; 652 if (rd instanceof InstantiableRelationDefinition<?, ?>) { 653 InstantiableRelationDefinition<?, ?> ir = (InstantiableRelationDefinition<?, ?>) rd; 654 userFriendlyPluralName = ir.getUserFriendlyPluralName(); 655 } else if (rd instanceof SetRelationDefinition<?, ?>) { 656 SetRelationDefinition<?, ?> sr = (SetRelationDefinition<?, ?>) rd; 657 userFriendlyPluralName = sr.getUserFriendlyPluralName(); 658 } 659 660 final MenuBuilder<Integer> builder = new MenuBuilder<>(app); 661 662 builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(userFriendlyName)); 663 builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get()); 664 665 if (lh != null) { 666 final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(lh); 667 final LocalizableMessage msg = getMsg( 668 INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR, userFriendlyName, 669 INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL, userFriendlyPluralName); 670 builder.addNumberedOption(msg, callback); 671 } 672 673 if (ch != null) { 674 final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(ch); 675 builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE.get(userFriendlyName), callback); 676 } 677 678 if (sh != null) { 679 final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(sh); 680 final LocalizableMessage msg = getMsg( 681 INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR, userFriendlyName, 682 INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL, userFriendlyPluralName); 683 builder.addNumberedOption(msg, callback); 684 } 685 686 if (dh != null) { 687 final SubCommandHandlerMenuCallback callback = new SubCommandHandlerMenuCallback(dh); 688 builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE.get(userFriendlyName), callback); 689 } 690 691 builder.addBackOption(true); 692 builder.addQuitOption(); 693 694 this.menu = builder.toMenu(); 695 } 696 697 private LocalizableMessage getMsg(Arg1<Object> singularMsg, LocalizableMessage userFriendlyName, 698 Arg1<Object> pluralMsg, LocalizableMessage userFriendlyPluralName) { 699 return userFriendlyPluralName != null 700 ? pluralMsg.get(userFriendlyPluralName) 701 : singularMsg.get(userFriendlyName); 702 } 703 704 @Override 705 public final MenuResult<Integer> invoke(ConsoleApplication app) throws ClientException { 706 try { 707 app.println(); 708 app.println(); 709 710 final MenuResult<Integer> result = menu.run(); 711 if (result.isCancel()) { 712 return MenuResult.again(); 713 } 714 return result; 715 } catch (ClientException e) { 716 app.errPrintln(e.getMessageObject()); 717 return MenuResult.success(1); 718 } 719 } 720 } 721 722 /** 723 * The type name which will be used for the most generic managed object types when they are instantiable and 724 * intended for customization only. 725 */ 726 public static final String CUSTOM_TYPE = "custom"; 727 728 /** 729 * The type name which will be used for the most generic managed object types when they are instantiable and not 730 * intended for customization. 731 */ 732 public static final String GENERIC_TYPE = "generic"; 733 734 /** 735 * Prints the provided error message if the provided application is interactive, 736 * throws a {@link ClientException} with provided error code and message otherwise. 737 * 738 * @param <T> 739 * The generic type parameter of the returned {@link MenuResult} 740 * @param app 741 * The console application where the message should be printed. 742 * @param msg 743 * The human readable error message. 744 * @param errorCode 745 * The operation error code. 746 * @return A generic cancel menu result if application is interactive. 747 * @throws ClientException 748 * If the application is not interactive. 749 */ 750 static <T> MenuResult<T> interactivePrintOrThrowError(ConsoleApplication app, 751 LocalizableMessage msg, ReturnCode errorCode) throws ClientException { 752 if (!app.isInteractive()) { 753 throw new ClientException(errorCode, msg); 754 } 755 app.errPrintln(); 756 app.errPrintln(msg); 757 return MenuResult.cancel(); 758 } 759 760 private long sessionStartTime; 761 private boolean sessionStartTimePrinted; 762 private int sessionEquivalentOperationNumber; 763 764 /** 765 * Provides the command-line arguments to the main application for processing. 766 * 767 * @param args 768 * The set of command-line arguments provided to this program. 769 */ 770 public static void main(String[] args) { 771 int exitCode = main(args, System.out, System.err); 772 if (exitCode != ReturnCode.SUCCESS.get()) { 773 System.exit(filterExitCode(exitCode)); 774 } 775 } 776 777 /** 778 * Provides the command-line arguments to the main application for processing and returns the exit code as an 779 * integer. 780 * 781 * @param args 782 * The set of command-line arguments provided to this program. 783 * @param outStream 784 * The output stream for standard output. 785 * @param errStream 786 * The output stream for standard error. 787 * @return Zero to indicate that the program completed successfully, or non-zero to indicate that an error occurred. 788 */ 789 public static int main(String[] args, OutputStream outStream, OutputStream errStream) { 790 disableLogging(); 791 final DSConfig app = new DSConfig(outStream, errStream); 792 app.sessionStartTime = System.currentTimeMillis(); 793 794 if (!ConfigurationFramework.getInstance().isInitialized()) { 795 try { 796 ConfigurationFramework.getInstance().initialize(); 797 } catch (ConfigException e) { 798 app.errPrintln(e.getMessageObject()); 799 return ReturnCode.ERROR_INITIALIZING_SERVER.get(); 800 } 801 } 802 803 // Run the application. 804 return app.run(args); 805 } 806 807 /** 808 * Prevent configuration framework to log on the console. 809 * @see OPENDJ-3140 for more details. 810 */ 811 private static void disableLogging() { 812 LogManager.getLogManager().reset(); 813 Logger.getLogger("").setLevel(Level.OFF); 814 } 815 816 /** The factory which the application should use to retrieve its management context. */ 817 private LDAPManagementContextFactory factory; 818 819 /** Flag indicating whether the global arguments have already been initialized. */ 820 private boolean globalArgumentsInitialized; 821 822 /** The sub-command handler factory. */ 823 private SubCommandHandlerFactory handlerFactory; 824 /** Mapping of sub-commands to their implementations. */ 825 private final Map<SubCommand, SubCommandHandler> handlers = new HashMap<>(); 826 /** Indicates whether a sub-command was provided. */ 827 private boolean hasSubCommand = true; 828 829 /** The command-line argument parser. */ 830 private final SubCommandArgumentParser parser; 831 832 /** The argument which should be used to request advanced mode. */ 833 private BooleanArgument advancedModeArgument; 834 /** The argument which should be used to request non interactive behavior. */ 835 private BooleanArgument noPromptArgument; 836 /** The argument that the user must set to display the equivalent non-interactive mode argument. */ 837 private BooleanArgument displayEquivalentArgument; 838 /** The argument that allows the user to dump the equivalent non-interactive command to a file. */ 839 private StringArgument equivalentCommandFileArgument; 840 841 /** The argument which should be used to request quiet output. */ 842 private BooleanArgument quietArgument; 843 /** The argument which should be used to request script-friendly output. */ 844 private BooleanArgument scriptFriendlyArgument; 845 /** The argument which should be used to request usage information. */ 846 private BooleanArgument showUsageArgument; 847 /** The argument which should be used to request verbose output. */ 848 private BooleanArgument verboseArgument; 849 850 /** The argument which should be used to read dsconfig commands from standard input. */ 851 private BooleanArgument batchArgument; 852 /** The argument which should be used to read dsconfig commands from a file. */ 853 private StringArgument batchFileArgument; 854 855 /** The argument which should be used to indicate the properties file. */ 856 private StringArgument propertiesFileArgument; 857 /** The argument which should be used to indicate that we will not look for properties file. */ 858 private BooleanArgument noPropertiesFileArgument; 859 860 /** 861 * Creates a new DSConfig application instance. 862 * 863 * @param out 864 * The application output stream. 865 * @param err 866 * The application error stream. 867 */ 868 private DSConfig(OutputStream out, OutputStream err) { 869 super(new PrintStream(out), new PrintStream(err)); 870 871 this.parser = new SubCommandArgumentParser(getClass().getName(), INFO_DSCFG_TOOL_DESCRIPTION.get(), false); 872 this.parser.setShortToolDescription(REF_SHORT_DESC_DSCONFIG.get()); 873 this.parser.setDocToolDescriptionSupplement(REF_DSCFG_DOC_TOOL_DESCRIPTION.get()); 874 this.parser.setDocSubcommandsDescriptionSupplement(REF_DSCFG_DOC_SUBCOMMANDS_DESCRIPTION.get()); 875 this.parser.setVersionHandler(newToolVersionHandler("opendj-config")); 876 if (System.getProperty("org.forgerock.opendj.gendoc") != null) { 877 this.parser.setUsageHandler(new DSConfigSubCommandUsageHandler()); 878 } 879 } 880 881 @Override 882 public boolean isAdvancedMode() { 883 return advancedModeArgument.isPresent(); 884 } 885 886 @Override 887 public boolean isInteractive() { 888 return !noPromptArgument.isPresent(); 889 } 890 891 @Override 892 public boolean isMenuDrivenMode() { 893 return !hasSubCommand; 894 } 895 896 @Override 897 public boolean isQuiet() { 898 return quietArgument.isPresent(); 899 } 900 901 @Override 902 public boolean isScriptFriendly() { 903 return scriptFriendlyArgument.isPresent(); 904 } 905 906 @Override 907 public boolean isVerbose() { 908 return verboseArgument.isPresent(); 909 } 910 911 /** 912 * Registers the global arguments with the argument parser. 913 * 914 * @throws ArgumentException 915 * If a global argument could not be registered. 916 */ 917 private void initializeGlobalArguments() throws ArgumentException { 918 if (!globalArgumentsInitialized) { 919 verboseArgument = verboseArgument(); 920 quietArgument = quietArgument(); 921 scriptFriendlyArgument = scriptFriendlyArgument(); 922 noPromptArgument = noPromptArgument(); 923 advancedModeArgument = advancedModeArgument(); 924 showUsageArgument = showUsageArgument(); 925 926 batchArgument = 927 BooleanArgument.builder(OPTION_LONG_BATCH) 928 .description(INFO_DESCRIPTION_BATCH.get()) 929 .buildArgument(); 930 batchFileArgument = 931 StringArgument.builder(OPTION_LONG_BATCH_FILE_PATH) 932 .shortIdentifier(OPTION_SHORT_BATCH_FILE_PATH) 933 .description(INFO_DESCRIPTION_BATCH_FILE_PATH.get()) 934 .valuePlaceholder(INFO_BATCH_FILE_PATH_PLACEHOLDER.get()) 935 .buildArgument(); 936 displayEquivalentArgument = 937 BooleanArgument.builder(OPTION_LONG_DISPLAY_EQUIVALENT) 938 .description(INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get()) 939 .buildArgument(); 940 equivalentCommandFileArgument = 941 StringArgument.builder(OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH) 942 .description(INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get()) 943 .valuePlaceholder(INFO_PATH_PLACEHOLDER.get()) 944 .buildArgument(); 945 propertiesFileArgument = 946 StringArgument.builder(OPTION_LONG_PROP_FILE_PATH) 947 .description(INFO_DESCRIPTION_PROP_FILE_PATH.get()) 948 .valuePlaceholder(INFO_PROP_FILE_PATH_PLACEHOLDER.get()) 949 .buildArgument(); 950 noPropertiesFileArgument = 951 BooleanArgument.builder(OPTION_LONG_NO_PROP_FILE) 952 .description(INFO_DESCRIPTION_NO_PROP_FILE.get()) 953 .buildArgument(); 954 955 // Register the global arguments. 956 ArgumentGroup toolOptionsGroup = new ArgumentGroup(INFO_DSCFG_DESCRIPTION_OPTIONS_ARGS.get(), 2); 957 parser.addGlobalArgument(advancedModeArgument, toolOptionsGroup); 958 959 parser.addGlobalArgument(showUsageArgument); 960 parser.setUsageArgument(showUsageArgument, getOutputStream()); 961 parser.addGlobalArgument(verboseArgument); 962 parser.addGlobalArgument(quietArgument); 963 parser.addGlobalArgument(scriptFriendlyArgument); 964 parser.addGlobalArgument(noPromptArgument); 965 parser.addGlobalArgument(batchArgument); 966 parser.addGlobalArgument(batchFileArgument); 967 parser.addGlobalArgument(displayEquivalentArgument); 968 parser.addGlobalArgument(equivalentCommandFileArgument); 969 parser.addGlobalArgument(propertiesFileArgument); 970 parser.setFilePropertiesArgument(propertiesFileArgument); 971 parser.addGlobalArgument(noPropertiesFileArgument); 972 parser.setNoPropertiesFileArgument(noPropertiesFileArgument); 973 974 globalArgumentsInitialized = true; 975 } 976 } 977 978 /** 979 * Registers the sub-commands with the argument parser. This method uses the administration framework introspection 980 * APIs to determine the overall structure of the command-line. 981 * 982 * @throws ArgumentException 983 * If a sub-command could not be created. 984 */ 985 private void initializeSubCommands() throws ArgumentException { 986 if (handlerFactory == null) { 987 handlerFactory = new SubCommandHandlerFactory(parser); 988 989 final Comparator<SubCommand> c = new Comparator<SubCommand>() { 990 991 @Override 992 public int compare(SubCommand o1, SubCommand o2) { 993 return o1.getName().compareTo(o2.getName()); 994 } 995 }; 996 997 Map<Tag, SortedSet<SubCommand>> groups = new TreeMap<>(); 998 SortedSet<SubCommand> allSubCommands = new TreeSet<>(c); 999 for (SubCommandHandler handler : handlerFactory.getAllSubCommandHandlers()) { 1000 SubCommand sc = handler.getSubCommand(); 1001 1002 handlers.put(sc, handler); 1003 allSubCommands.add(sc); 1004 1005 // Add the sub-command to its groups. 1006 for (Tag tag : handler.getTags()) { 1007 SortedSet<SubCommand> group = groups.get(tag); 1008 if (group == null) { 1009 group = new TreeSet<>(c); 1010 groups.put(tag, group); 1011 } 1012 group.add(sc); 1013 } 1014 } 1015 1016 // Register the usage arguments. 1017 for (Map.Entry<Tag, SortedSet<SubCommand>> group : groups.entrySet()) { 1018 Tag tag = group.getKey(); 1019 SortedSet<SubCommand> subCommands = group.getValue(); 1020 1021 String option = OPTION_LONG_HELP + "-" + tag.getName(); 1022 String synopsis = tag.getSynopsis().toString().toLowerCase(); 1023 BooleanArgument arg = 1024 BooleanArgument.builder(option) 1025 .description(INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE.get(synopsis)) 1026 .buildArgument(); 1027 1028 parser.addGlobalArgument(arg); 1029 parser.setUsageGroupArgument(arg, subCommands); 1030 } 1031 1032 // Register the --help-all argument. 1033 String option = OPTION_LONG_HELP + "-all"; 1034 BooleanArgument arg = 1035 BooleanArgument.builder(option) 1036 .description(INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL.get()) 1037 .buildArgument(); 1038 1039 parser.addGlobalArgument(arg); 1040 parser.setUsageGroupArgument(arg, allSubCommands); 1041 } 1042 } 1043 1044 /** 1045 * Parses the provided command-line arguments and makes the appropriate changes to the Directory Server 1046 * configuration. 1047 * 1048 * @param args 1049 * The command-line arguments provided to this program. 1050 * @return The exit code from the configuration processing. A nonzero value indicates that there was some kind of 1051 * problem during the configuration processing. 1052 */ 1053 private int run(String[] args) { 1054 // Register global arguments and sub-commands. 1055 try { 1056 initializeGlobalArguments(); 1057 initializeSubCommands(); 1058 } catch (ArgumentException e) { 1059 errPrintln(ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage())); 1060 return ReturnCode.ERROR_USER_DATA.get(); 1061 } 1062 1063 ConnectionFactoryProvider cfp = null; 1064 try { 1065 cfp = new ConnectionFactoryProvider(parser, this, CliConstants.DEFAULT_ROOT_USER_DN, 1066 CliConstants.DEFAULT_ADMINISTRATION_CONNECTOR_PORT, true); 1067 cfp.setIsAnAdminConnection(); 1068 1069 // Parse the command-line arguments provided to this program. 1070 parser.parseArguments(args); 1071 checkForConflictingArguments(); 1072 } catch (ArgumentException ae) { 1073 parser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 1074 return ReturnCode.CONFLICTING_ARGS.get(); 1075 } 1076 1077 // If the usage/version argument was provided, then we don't need 1078 // to do anything else. 1079 if (parser.usageOrVersionDisplayed()) { 1080 return ReturnCode.SUCCESS.get(); 1081 } 1082 1083 // Check that we can write on the provided path where we write the 1084 // equivalent non-interactive commands. 1085 if (equivalentCommandFileArgument.isPresent()) { 1086 final String file = equivalentCommandFileArgument.getValue(); 1087 if (!canWrite(file)) { 1088 errPrintln(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file)); 1089 return ReturnCode.ERROR_UNEXPECTED.get(); 1090 } else if (new File(file).isDirectory()) { 1091 errPrintln(ERR_DSCFG_EQUIVALENT_COMMAND_LINE_FILE_DIRECTORY.get(file)); 1092 return ReturnCode.ERROR_UNEXPECTED.get(); 1093 } 1094 } 1095 // Creates the management context factory which is based on the connection 1096 // provider factory and an authenticated connection factory. 1097 try { 1098 factory = new LDAPManagementContextFactory(cfp); 1099 } catch (ArgumentException e) { 1100 parser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(e.getMessage())); 1101 return ReturnCode.CONFLICTING_ARGS.get(); 1102 } 1103 1104 try { 1105 // Handle batch file if any 1106 if (batchArgument.isPresent() || batchFileArgument.isPresent()) { 1107 handleBatch(args); 1108 return ReturnCode.SUCCESS.get(); 1109 } 1110 1111 hasSubCommand = parser.getSubCommand() != null; 1112 if (hasSubCommand) { 1113 // Retrieve the sub-command implementation and run it. 1114 return runSubCommand(handlers.get(parser.getSubCommand())); 1115 } else if (isInteractive()) { 1116 // Top-level interactive mode. 1117 return runInteractiveMode(); 1118 } else { 1119 parser.displayMessageAndUsageReference( 1120 getErrStream(), ERR_ERROR_PARSING_ARGS.get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get())); 1121 return ReturnCode.ERROR_USER_DATA.get(); 1122 } 1123 } finally { 1124 factory.close(); 1125 } 1126 } 1127 1128 private void checkForConflictingArguments() throws ArgumentException { 1129 throwIfArgumentsConflict(quietArgument, verboseArgument); 1130 throwIfArgumentsConflict(batchArgument, batchFileArgument); 1131 1132 throwIfSetInInteractiveMode(batchFileArgument); 1133 throwIfSetInInteractiveMode(batchArgument); 1134 throwIfSetInInteractiveMode(quietArgument); 1135 1136 throwIfArgumentsConflict(scriptFriendlyArgument, verboseArgument); 1137 throwIfArgumentsConflict(noPropertiesFileArgument, propertiesFileArgument); 1138 } 1139 1140 private void throwIfSetInInteractiveMode(Argument arg) throws ArgumentException { 1141 if (arg.isPresent() && !noPromptArgument.isPresent()) { 1142 throw new ArgumentException(ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get( 1143 arg.getLongIdentifier(), noPromptArgument.getLongIdentifier())); 1144 } 1145 } 1146 1147 /** Run the top-level interactive console. */ 1148 private int runInteractiveMode() { 1149 ConsoleApplication app = this; 1150 1151 // Build menu structure. 1152 final Comparator<RelationDefinition<?, ?>> c = new Comparator<RelationDefinition<?, ?>>() { 1153 1154 @Override 1155 public int compare(RelationDefinition<?, ?> rd1, RelationDefinition<?, ?> rd2) { 1156 final String s1 = rd1.getUserFriendlyName().toString(); 1157 final String s2 = rd2.getUserFriendlyName().toString(); 1158 1159 return s1.compareToIgnoreCase(s2); 1160 } 1161 }; 1162 1163 final Set<RelationDefinition<?, ?>> relations = new TreeSet<>(c); 1164 1165 final Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers = new HashMap<>(); 1166 final Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers = new HashMap<>(); 1167 final Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers = new HashMap<>(); 1168 final Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers = new HashMap<>(); 1169 final Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers = new HashMap<>(); 1170 1171 for (final CreateSubCommandHandler<?, ?> ch : handlerFactory.getCreateSubCommandHandlers()) { 1172 relations.add(ch.getRelationDefinition()); 1173 createHandlers.put(ch.getRelationDefinition(), ch); 1174 } 1175 1176 for (final DeleteSubCommandHandler dh : handlerFactory.getDeleteSubCommandHandlers()) { 1177 relations.add(dh.getRelationDefinition()); 1178 deleteHandlers.put(dh.getRelationDefinition(), dh); 1179 } 1180 1181 for (final ListSubCommandHandler lh : handlerFactory.getListSubCommandHandlers()) { 1182 relations.add(lh.getRelationDefinition()); 1183 listHandlers.put(lh.getRelationDefinition(), lh); 1184 } 1185 1186 for (final GetPropSubCommandHandler gh : handlerFactory.getGetPropSubCommandHandlers()) { 1187 relations.add(gh.getRelationDefinition()); 1188 getPropHandlers.put(gh.getRelationDefinition(), gh); 1189 } 1190 1191 for (final SetPropSubCommandHandler sh : handlerFactory.getSetPropSubCommandHandlers()) { 1192 relations.add(sh.getRelationDefinition()); 1193 setPropHandlers.put(sh.getRelationDefinition(), sh); 1194 } 1195 1196 // Main menu. 1197 final MenuBuilder<Integer> builder = new MenuBuilder<>(app); 1198 1199 builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get()); 1200 builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get()); 1201 builder.setMultipleColumnThreshold(0); 1202 1203 for (final RelationDefinition<?, ?> rd : relations) { 1204 final MenuCallback<Integer> callback = new SubMenuCallback(app, rd, createHandlers.get(rd), 1205 deleteHandlers.get(rd), listHandlers.get(rd), setPropHandlers.get(rd)); 1206 builder.addNumberedOption(rd.getUserFriendlyName(), callback); 1207 } 1208 1209 builder.addQuitOption(); 1210 1211 final Menu<Integer> menu = builder.toMenu(); 1212 1213 try { 1214 // Force retrieval of management context. 1215 factory.getManagementContext(); 1216 } catch (ArgumentException e) { 1217 parser.displayMessageAndUsageReference(getErrStream(), e.getMessageObject()); 1218 return ReturnCode.ERROR_USER_DATA.get(); 1219 } catch (ClientException e) { 1220 app.errPrintln(e.getMessageObject()); 1221 return ReturnCode.ERROR_UNEXPECTED.get(); 1222 } 1223 1224 try { 1225 app.println(); 1226 app.println(); 1227 1228 final MenuResult<Integer> result = menu.run(); 1229 if (result.isQuit()) { 1230 return ReturnCode.SUCCESS.get(); 1231 } else { 1232 return result.getValue(); 1233 } 1234 } catch (ClientException e) { 1235 app.errPrintln(e.getMessageObject()); 1236 return ReturnCode.ERROR_UNEXPECTED.get(); 1237 } 1238 } 1239 1240 /** Run the provided sub-command handler. */ 1241 private int runSubCommand(SubCommandHandler handler) { 1242 try { 1243 final MenuResult<Integer> result = handler.run(this, factory); 1244 if (result.isSuccess()) { 1245 if (isInteractive() && handler.isCommandBuilderUseful()) { 1246 printCommandBuilder(getCommandBuilder(handler)); 1247 } 1248 return result.getValue(); 1249 } else { 1250 // User must have quit. 1251 return ReturnCode.ERROR_UNEXPECTED.get(); 1252 } 1253 } catch (ArgumentException e) { 1254 errPrintln(e.getMessageObject()); 1255 return ReturnCode.ERROR_UNEXPECTED.get(); 1256 } catch (ClientException e) { 1257 Throwable cause = e.getCause(); 1258 errPrintln(); 1259 if (cause instanceof ManagedObjectDecodingException) { 1260 displayManagedObjectDecodingException(this, (ManagedObjectDecodingException) cause); 1261 } else if (cause instanceof MissingMandatoryPropertiesException) { 1262 displayMissingMandatoryPropertyException(this, (MissingMandatoryPropertiesException) cause); 1263 } else if (cause instanceof OperationRejectedException) { 1264 displayOperationRejectedException(this, (OperationRejectedException) cause); 1265 } else { 1266 // Just display the default message. 1267 errPrintln(e.getMessageObject()); 1268 } 1269 errPrintln(); 1270 1271 return ReturnCode.ERROR_UNEXPECTED.get(); 1272 } catch (Exception e) { 1273 errPrintln(LocalizableMessage.raw(stackTraceToSingleLineString(e, true))); 1274 return ReturnCode.ERROR_UNEXPECTED.get(); 1275 } 1276 } 1277 1278 /** 1279 * Updates the command builder with the global options: script friendly, verbose, etc. for a given sub command. It 1280 * also adds systematically the no-prompt option. 1281 * 1282 * @param subCommand 1283 * The sub command handler or common. 1284 * @return <T> The builded command. 1285 */ 1286 CommandBuilder getCommandBuilder(final Object subCommand) { 1287 final String commandName = getScriptName(); 1288 final SubCommandHandler handler; 1289 final String subCommandName; 1290 if (subCommand instanceof SubCommandHandler) { 1291 handler = (SubCommandHandler) subCommand; 1292 subCommandName = handler.getSubCommand().getName(); 1293 } else { 1294 handler = null; 1295 subCommandName = (String) subCommand; 1296 } 1297 1298 final CommandBuilder commandBuilder = new CommandBuilder(commandName, subCommandName); 1299 if (handler != null) { 1300 commandBuilder.append(handler.getCommandBuilder()); 1301 } 1302 if (factory != null && factory.getContextCommandBuilder() != null) { 1303 commandBuilder.append(factory.getContextCommandBuilder()); 1304 } 1305 if (verboseArgument.isPresent()) { 1306 commandBuilder.addArgument(verboseArgument); 1307 } 1308 if (scriptFriendlyArgument.isPresent()) { 1309 commandBuilder.addArgument(scriptFriendlyArgument); 1310 } 1311 1312 commandBuilder.addArgument(noPromptArgument); 1313 1314 if (propertiesFileArgument.isPresent()) { 1315 commandBuilder.addArgument(propertiesFileArgument); 1316 } 1317 if (noPropertiesFileArgument.isPresent()) { 1318 commandBuilder.addArgument(noPropertiesFileArgument); 1319 } 1320 1321 return commandBuilder; 1322 } 1323 1324 private String getScriptName() { 1325 final String commandName = System.getProperty(PROPERTY_SCRIPT_NAME); 1326 if (commandName != null && commandName.length() != 0) { 1327 return commandName; 1328 } 1329 return DSCONFIGTOOLNAME; 1330 } 1331 1332 /** 1333 * Prints the contents of a command builder. This method has been created since SetPropSubCommandHandler calls it. 1334 * All the logic of DSConfig is on this method. It writes the content of the CommandBuilder to the standard output, 1335 * or to a file depending on the options provided by the user. 1336 * 1337 * @param commandBuilder 1338 * the command builder to be printed. 1339 */ 1340 void printCommandBuilder(CommandBuilder commandBuilder) { 1341 if (displayEquivalentArgument.isPresent()) { 1342 println(); 1343 // We assume that the app we are running is this one. 1344 println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder)); 1345 } 1346 if (equivalentCommandFileArgument.isPresent()) { 1347 String file = equivalentCommandFileArgument.getValue(); 1348 BufferedWriter writer = null; 1349 try { 1350 writer = new BufferedWriter(new FileWriter(file, true)); 1351 1352 if (!sessionStartTimePrinted) { 1353 writer.write(SHELL_COMMENT_SEPARATOR + getSessionStartTimeMessage()); 1354 writer.newLine(); 1355 sessionStartTimePrinted = true; 1356 } 1357 1358 sessionEquivalentOperationNumber++; 1359 writer.newLine(); 1360 writer.write(SHELL_COMMENT_SEPARATOR 1361 + INFO_DSCFG_EQUIVALENT_COMMAND_LINE_SESSION_OPERATION_NUMBER 1362 .get(sessionEquivalentOperationNumber)); 1363 writer.newLine(); 1364 1365 writer.write(SHELL_COMMENT_SEPARATOR + getCurrentOperationDateMessage()); 1366 writer.newLine(); 1367 1368 writer.write(commandBuilder.toString()); 1369 writer.newLine(); 1370 writer.newLine(); 1371 1372 writer.flush(); 1373 } catch (IOException ioe) { 1374 errPrintln(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file, ioe)); 1375 } finally { 1376 closeSilently(writer); 1377 } 1378 } 1379 } 1380 1381 /** 1382 * Returns the message to be displayed in the file with the equivalent command-line with information about when the 1383 * session started. 1384 * 1385 * @return the message to be displayed in the file with the equivalent command-line with information about when the 1386 * session started. 1387 */ 1388 private String getSessionStartTimeMessage() { 1389 final String date = formatDateTimeStringForEquivalentCommand(new Date(sessionStartTime)); 1390 return INFO_DSCFG_SESSION_START_TIME_MESSAGE.get(getScriptName(), date).toString(); 1391 } 1392 1393 private void handleBatch(String[] args) { 1394 BufferedReader bReader = null; 1395 try { 1396 if (batchArgument.isPresent()) { 1397 bReader = new BufferedReader(new InputStreamReader(System.in)); 1398 } else if (batchFileArgument.isPresent()) { 1399 final String batchFilePath = batchFileArgument.getValue().trim(); 1400 bReader = new BufferedReader(new FileReader(batchFilePath)); 1401 } else { 1402 throw new IllegalArgumentException("Either --" + OPTION_LONG_BATCH 1403 + " or --" + OPTION_LONG_BATCH_FILE_PATH + " argument should have been set"); 1404 } 1405 1406 List<String> initialArgs = removeBatchArgs(args); 1407 1408 // Split the CLI string into arguments array 1409 String command = ""; 1410 String line; 1411 while ((line = bReader.readLine()) != null) { 1412 if ("".equals(line) || line.startsWith("#")) { 1413 // Empty line or comment 1414 continue; 1415 } 1416 // command split in several line support 1417 if (line.endsWith("\\")) { 1418 // command is split into several lines 1419 command += line.substring(0, line.length() - 1); 1420 continue; 1421 } 1422 1423 command += line; 1424 command = command.trim(); 1425 // string between quotes support 1426 command = replaceSpacesInQuotes(command); 1427 // "\ " support 1428 command = command.replace("\\ ", "##"); 1429 1430 String displayCommand = command.replace("\\ ", " "); 1431 println(LocalizableMessage.raw(displayCommand)); 1432 1433 // Append initial arguments to the file line 1434 final String[] allArgsArray = buildCommandArgs(initialArgs, command); 1435 int exitCode = main(allArgsArray, getOutputStream(), getErrorStream()); 1436 if (exitCode != ReturnCode.SUCCESS.get()) { 1437 System.exit(filterExitCode(exitCode)); 1438 } 1439 println(); 1440 // reset command 1441 command = ""; 1442 } 1443 } catch (IOException ex) { 1444 errPrintln(ERR_DSCFG_ERROR_READING_BATCH_FILE.get(ex)); 1445 } finally { 1446 closeSilently(bReader); 1447 } 1448 } 1449 1450 private String[] buildCommandArgs(List<String> initialArgs, String batchCommand) { 1451 final String[] commandArgs = toCommandArgs(batchCommand); 1452 final int length = commandArgs.length + initialArgs.size(); 1453 final List<String> allArguments = new ArrayList<>(length); 1454 Collections.addAll(allArguments, commandArgs); 1455 allArguments.addAll(initialArgs); 1456 return allArguments.toArray(new String[length]); 1457 } 1458 1459 private String[] toCommandArgs(String command) { 1460 String[] fileArguments = command.split("\\s+"); 1461 for (int ii = 0; ii < fileArguments.length; ii++) { 1462 fileArguments[ii] = fileArguments[ii].replace("##", " "); 1463 } 1464 return fileArguments; 1465 } 1466 1467 private List<String> removeBatchArgs(String[] args) { 1468 // Build a list of initial arguments, 1469 // removing the batch file option + its value 1470 final List<String> initialArgs = new ArrayList<>(); 1471 Collections.addAll(initialArgs, args); 1472 for (Iterator<String> it = initialArgs.iterator(); it.hasNext();) { 1473 final String elem = it.next(); 1474 if (batchArgument.isPresent() 1475 && elem.contains(batchArgument.getLongIdentifier())) { 1476 it.remove(); 1477 break; 1478 } else if (batchFileArgument.isPresent() 1479 && (elem.startsWith("-" + batchFileArgument.getShortIdentifier()) 1480 || elem.contains(batchFileArgument.getLongIdentifier()))) { 1481 // Remove both the batch file arg and its value 1482 it.remove(); 1483 it.next(); 1484 it.remove(); 1485 break; 1486 } 1487 } 1488 return initialArgs; 1489 } 1490 1491 /** Replace spaces in quotes by "\ ". */ 1492 private String replaceSpacesInQuotes(final String line) { 1493 StringBuilder newLine = new StringBuilder(); 1494 boolean inQuotes = false; 1495 for (int ii = 0; ii < line.length(); ii++) { 1496 char ch = line.charAt(ii); 1497 if (ch == '\"' || ch == '\'') { 1498 inQuotes = !inQuotes; 1499 continue; 1500 } 1501 if (inQuotes && ch == ' ') { 1502 newLine.append("\\ "); 1503 } else { 1504 newLine.append(ch); 1505 } 1506 } 1507 return newLine.toString(); 1508 } 1509}