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