/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.tools.dsconfig; import static org.opends.server.loggers.debug.DebugLogger.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.messages.ToolMessages.*; import static org.opends.server.tools.ToolConstants.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.opends.server.admin.AbstractManagedObjectDefinition; import org.opends.server.admin.AttributeTypePropertyDefinition; import org.opends.server.admin.ClassLoaderProvider; import org.opends.server.admin.ClassPropertyDefinition; import org.opends.server.admin.PropertyException; import org.opends.server.admin.Tag; import org.opends.server.admin.client.ManagedObjectDecodingException; import org.opends.server.admin.client.ManagementContext; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.tools.ClientException; import org.opends.server.types.DebugLogLevel; import org.opends.server.types.InitializationException; import org.opends.server.types.NullOutputStream; import org.opends.server.util.PasswordReader; import org.opends.server.util.StaticUtils; import org.opends.server.util.args.ArgumentException; import org.opends.server.util.args.BooleanArgument; import org.opends.server.util.args.SubCommand; import org.opends.server.util.args.SubCommandArgumentParser; /** * This class provides a command-line tool which enables * administrators to configure the Directory Server. */ public final class DSConfig { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * Provides the command-line arguments to the main application for * processing. * * @param args * The set of command-line arguments provided to this * program. */ public static void main(String[] args) { DSConfig app = new DSConfig(System.in, System.out, System.err, new LDAPManagementContextFactory()); // Only initialize the client environment when run as a standalone // application. try { app.initializeClientEnvironment(); } catch (InitializationException e) { // TODO: is this ok as an error message? System.err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH)); System.exit(1); } // Run the application. int exitCode = app.run(args); if (exitCode != 0) { System.exit(filterExitCode(exitCode)); } } // Flag indicating whether or not the application environment has // already been initialized. private boolean environmentInitialized = false; // The error stream which this application should use. private final PrintStream err; // The factory which the application should use to retrieve its // management context. private final ManagementContextFactory factory; // Flag indicating whether or not the global arguments have // already been initialized. private boolean globalArgumentsInitialized = false; // Mapping of sub-commands to their implementations; private final Map handlers = new HashMap(); // The input stream reader which this application should use. private final BufferedReader in; // The argument which should be used to request interactive // behavior. private BooleanArgument interactiveArgument; // The output stream which this application should use. private final PrintStream out; // The command-line argument parser. private final SubCommandArgumentParser parser; // The argument which should be used to request quiet output. private BooleanArgument quietArgument; // The argument which should be used to request script-friendly // output. private BooleanArgument scriptFriendlyArgument; // The argument which should be used to request usage information. private BooleanArgument showUsageArgument; // Flag indicating whether or not the sub-commands have // already been initialized. private boolean subCommandsInitialized = false; // The argument which should be used to request verbose output. private BooleanArgument verboseArgument; /** * Creates a new dsconfig application instance. * * @param in * The application input stream. * @param out * The application output stream. * @param err * The application error stream. * @param factory * The factory which this application instance should use * for obtaining management contexts. */ public DSConfig(InputStream in, OutputStream out, OutputStream err, ManagementContextFactory factory) { this.parser = new SubCommandArgumentParser(this.getClass().getName(), getMessage(MSGID_CONFIGDS_TOOL_DESCRIPTION), false); if (in != null) { this.in = new BufferedReader(new InputStreamReader(in)); } else { this.in = new BufferedReader(new Reader() { @Override public void close() throws IOException { // Do nothing. } @Override public int read(char[] cbuf, int off, int len) throws IOException { return -1; } }); } if (out != null) { this.out = new PrintStream(out); } else { this.out = NullOutputStream.printStream(); } if (err != null) { this.err = new PrintStream(err); } else { this.err = NullOutputStream.printStream(); } this.factory = factory; } /** * Interactively confirms whether a user wishes to perform an * action. If the application is non-interactive, then the action is * granted by default. * * @param prompt * The prompt describing the action. * @return Returns true if the user wishes the action * to be performed, or false if they refused, * or if an exception occurred. */ public boolean confirmAction(String prompt) { if (!isInteractive()) { return true; } String yes = Messages.getString("general.yes"); String no = Messages.getString("general.no"); String errMsg = Messages.getString("general.confirm.error"); String error = String.format(errMsg, yes, no); prompt = prompt + String.format(" (%s / %s): ", yes, no); while (true) { String response; try { response = readLineOfInput(prompt); } catch (Exception e) { return false; } if (response == null) { // End of input. return false; } response = response.toLowerCase().trim(); if (response.length() == 0) { // Empty input. err.println(wrapText(error, MAX_LINE_WIDTH)); } else if (no.startsWith(response)) { return false; } else if (yes.startsWith(response)) { return true; } else { // Try again... err.println(wrapText(error, MAX_LINE_WIDTH)); } } } /** * Displays a message to the error stream. * * @param msg * The message. */ public void displayMessage(String msg) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } /** * Displays a message to the error stream if verbose mode is * enabled. * * @param msg * The verbose message. */ public void displayVerboseMessage(String msg) { if (isVerbose()) { err.println(wrapText(msg, MAX_LINE_WIDTH)); } } /** * Initializes core APIs for use when dsconfig will be run as a * standalone application. * * @throws InitializationException * If the core APIs could not be initialized. */ public void initializeClientEnvironment() throws InitializationException { if (environmentInitialized == false) { // TODO: do we need to do this? DirectoryServer.bootstrapClient(); // Bootstrap definition classes. ClassLoaderProvider.getInstance().enable(); // Switch off class name validation in client. ClassPropertyDefinition.setAllowClassValidation(false); // Switch off attribute type name validation in client. AttributeTypePropertyDefinition.setCheckSchema(false); environmentInitialized = true; } } /** * Indicates whether or not the user has requested interactive * behavior. * * @return Returns true if the user has requested * interactive behavior. */ public boolean isInteractive() { return interactiveArgument.isPresent(); } /** * Indicates whether or not the user has requested quiet output. * * @return Returns true if the user has requested * quiet output. */ public boolean isQuiet() { return quietArgument.isPresent(); } /** * Indicates whether or not the user has requested script-friendly * output. * * @return Returns true if the user has requested * script-friendly output. */ public boolean isScriptFriendly() { return scriptFriendlyArgument.isPresent(); } /** * Indicates whether or not the user has requested verbose output. * * @return Returns true if the user has requested * verbose output. */ public boolean isVerbose() { return verboseArgument.isPresent(); } /** * Interactively retrieves a line of input from the console. * * @param prompt * The prompt. * @return Returns the line of input, or null if the * end of input has been reached. * @throws Exception * If the line of input could not be retrieved for some * reason. */ public String readLineOfInput(String prompt) throws Exception { err.print(wrapText(prompt, MAX_LINE_WIDTH)); return in.readLine(); } /** * Interactively retrieves a password from the console. * * @param prompt * The password prompt. * @return Returns the password. * @throws Exception * If the password could not be retrieved for some reason. */ public String readPassword(String prompt) throws Exception { err.print(wrapText(prompt, MAX_LINE_WIDTH)); char[] pwChars = PasswordReader.readPassword(); return new String(pwChars); } /** * Gets the management context which sub-commands should use in * order to manage the directory server. * * @return Returns the management context which sub-commands should * use in order to manage the directory server. * @throws ArgumentException * If a management context related argument could not be * parsed successfully. * @throws ClientException * If the management context could not be created. */ ManagementContext getManagementContext() throws ArgumentException, ClientException { return factory.getManagementContext(this); } /** * Registers the global arguments with the argument parser. * * @throws ArgumentException * If a global argument could not be registered. */ private void initializeGlobalArguments() throws ArgumentException { if (globalArgumentsInitialized == false) { verboseArgument = new BooleanArgument("verbose", 'v', "verbose", MSGID_DESCRIPTION_VERBOSE); quietArgument = new BooleanArgument("quiet", 'q', "quiet", MSGID_DESCRIPTION_QUIET); scriptFriendlyArgument = new BooleanArgument("script-friendly", 's', "script-friendly", MSGID_DESCRIPTION_SCRIPT_FRIENDLY); interactiveArgument = new BooleanArgument("interactive", 'i', "interactive", MSGID_DESCRIPTION_INTERACTIVE); showUsageArgument = new BooleanArgument("showUsage", OPTION_SHORT_HELP, OPTION_LONG_HELP, MSGID_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY); // Register the global arguments. parser.addGlobalArgument(showUsageArgument); parser.setUsageArgument(showUsageArgument, out); parser.addGlobalArgument(verboseArgument); parser.addGlobalArgument(quietArgument); parser.addGlobalArgument(scriptFriendlyArgument); parser.addGlobalArgument(interactiveArgument); // Register any global arguments required by the management // context factory. factory.registerGlobalArguments(parser); globalArgumentsInitialized = true; } } /** * Registers the sub-commands with the argument parser. This method * uses the administration framework introspection APIs to determine * the overall structure of the command-line. * * @throws ArgumentException * If a sub-command could not be created. */ private void initializeSubCommands() throws ArgumentException { if (subCommandsInitialized == false) { Comparator c = new Comparator() { public int compare(SubCommand o1, SubCommand o2) { return o1.getName().compareTo(o2.getName()); } }; SubCommandBuilder builder = new SubCommandBuilder(); Map> groups = new TreeMap>(); SortedSet allSubCommands = new TreeSet(c); for (SubCommandHandler handler : builder.getSubCommandHandlers(parser)) { SubCommand sc = handler.getSubCommand(); handlers.put(sc, handler); allSubCommands.add(sc); // Add the sub-command to its groups. for (Tag tag : handler.getTags()) { SortedSet group = groups.get(tag); if (group == null) { group = new TreeSet(c); groups.put(tag, group); } group.add(sc); } } // Register the usage arguments. for (Map.Entry> group : groups.entrySet()) { Tag tag = group.getKey(); SortedSet subCommands = group.getValue(); String option = OPTION_LONG_HELP + "-" + tag.getName(); String synopsis = tag.getSynopsis().toLowerCase(); BooleanArgument arg = new BooleanArgument(option, null, option, MSGID_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE, synopsis); parser.addGlobalArgument(arg); parser.setUsageGroupArgument(arg, subCommands); } // Register the --help-all argument. String option = OPTION_LONG_HELP + "-all"; BooleanArgument arg = new BooleanArgument(option, null, option, MSGID_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL); parser.addGlobalArgument(arg); parser.setUsageGroupArgument(arg, allSubCommands); subCommandsInitialized = true; } } /** * Parses the provided command-line arguments and makes the * appropriate changes to the Directory Server configuration. * * @param args * The command-line arguments provided to this program. * @return The exit code from the configuration processing. A * nonzero value indicates that there was some kind of * problem during the configuration processing. */ private int run(String[] args) { // Register global arguments and sub-commands. try { initializeGlobalArguments(); initializeSubCommands(); } catch (ArgumentException e) { int msgID = MSGID_CANNOT_INITIALIZE_ARGS; String message = getMessage(msgID, e.getMessage()); err.println(wrapText(message, MAX_LINE_WIDTH)); return 1; } // Parse the command-line arguments provided to this program. try { parser.parseArguments(args); } catch (ArgumentException ae) { int msgID = MSGID_ERROR_PARSING_ARGS; String message = getMessage(msgID, ae.getMessage()); err.println(wrapText(message, MAX_LINE_WIDTH)); err.println(); err.println(parser.getHelpUsageReference()); return 1; } // If the usage/version argument was provided, then we don't need // to do anything else. if (parser.usageOrVersionDisplayed()) { return 0; } // Make sure that we have a sub-command. if (parser.getSubCommand() == null) { int msgID = MSGID_ERROR_PARSING_ARGS; String message = getMessage(msgID, getMessage(MSGID_DSCFG_ERROR_MISSING_SUBCOMMAND)); err.println(wrapText(message, MAX_LINE_WIDTH)); err.println(); err.println(parser.getHelpUsageReference()); return 1; } if (quietArgument.isPresent() && verboseArgument.isPresent()) { int msgID = MSGID_TOOL_CONFLICTING_ARGS; String message = getMessage(msgID, quietArgument.getLongIdentifier(), verboseArgument.getLongIdentifier()); err.println(wrapText(message, MAX_LINE_WIDTH)); err.println(); err.println(parser.getHelpUsageReference()); return 1; } if (quietArgument.isPresent() && interactiveArgument.isPresent()) { int msgID = MSGID_TOOL_CONFLICTING_ARGS; String message = getMessage(msgID, quietArgument.getLongIdentifier(), interactiveArgument.getLongIdentifier()); err.println(wrapText(message, MAX_LINE_WIDTH)); err.println(); err.println(parser.getHelpUsageReference()); return 1; } if (scriptFriendlyArgument.isPresent() && verboseArgument.isPresent()) { int msgID = MSGID_TOOL_CONFLICTING_ARGS; String message = getMessage(msgID, scriptFriendlyArgument .getLongIdentifier(), verboseArgument.getLongIdentifier()); err.println(wrapText(message, MAX_LINE_WIDTH)); err.println(); err.println(parser.getHelpUsageReference()); return 1; } // Make sure that management context's arguments are valid. try { factory.validateGlobalArguments(); } catch (ArgumentException e) { err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH)); return 1; } // Retrieve the sub-command implementation and run it. SubCommandHandler handler = handlers.get(parser.getSubCommand()); try { return handler.run(this, out, err); } catch (ArgumentException e) { err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH)); return 1; } catch (ClientException e) { // If the client exception was caused by a decoding exception // then we should display the causes. err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH)); Throwable cause = e.getCause(); if (cause instanceof ManagedObjectDecodingException) { // FIXME: use a table. ManagedObjectDecodingException de = (ManagedObjectDecodingException) cause; err.println(); for (PropertyException pe : de.getCauses()) { AbstractManagedObjectDefinition d = de .getPartialManagedObject().getManagedObjectDefinition(); ArgumentException ae = ArgumentExceptionFactory .adaptPropertyException(pe, d); err.println(wrapText(" * " + ae.getMessage(), MAX_LINE_WIDTH)); } err.println(); } return 1; } catch (Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } err.println(wrapText(StaticUtils.stackTraceToString(e), MAX_LINE_WIDTH)); return 1; } } }