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