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