/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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
*
*
* Copyright 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2012-2013 ForgeRock AS
*/
package org.opends.server.tools.dsconfig;
import java.io.BufferedReader;
import static org.opends.messages.DSConfigMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.tools.ToolConstants.*;
import static org.opends.server.tools.dsconfig.ArgumentExceptionFactory.*;
import static org.opends.server.util.ServerConstants.PROPERTY_SCRIPT_NAME;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.opends.messages.Message;
import org.opends.quicksetup.util.Utils;
import org.opends.server.admin.AttributeTypePropertyDefinition;
import org.opends.server.admin.ClassLoaderProvider;
import org.opends.server.admin.ClassPropertyDefinition;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.SetRelationDefinition;
import org.opends.server.admin.Tag;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.MissingMandatoryPropertiesException;
import org.opends.server.admin.client.OperationRejectedException;
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.util.BuildVersion;
import org.opends.server.util.EmbeddedUtils;
import org.opends.server.util.ServerConstants;
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.StringArgument;
import org.opends.server.util.args.SubCommand;
import org.opends.server.util.args.SubCommandArgumentParser;
import org.opends.server.util.args.ArgumentGroup;
import org.opends.server.util.cli.CLIException;
import org.opends.server.util.cli.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.Menu;
import org.opends.server.util.cli.MenuBuilder;
import org.opends.server.util.cli.MenuCallback;
import org.opends.server.util.cli.MenuResult;
import org.opends.server.util.cli.OutputStreamConsoleApplication;
/**
* This class provides a command-line tool which enables
* administrators to configure the Directory Server.
*/
public final class DSConfig extends ConsoleApplication {
/**
* A menu call-back which runs a sub-command interactively.
*/
private class SubCommandHandlerMenuCallback implements MenuCallback<Integer> {
// The sub-command handler.
private final SubCommandHandler handler;
/**
* Creates a new sub-command handler call-back.
*
* @param handler
* The sub-command handler.
*/
public SubCommandHandlerMenuCallback(SubCommandHandler handler) {
this.handler = handler;
}
/**
* {@inheritDoc}
*/
@Override
public MenuResult<Integer> invoke(ConsoleApplication app)
throws CLIException {
try {
MenuResult<Integer> result = handler.run(app, factory);
if (result.isQuit()) {
return result;
} else {
if (result.isSuccess() && isInteractive() &&
handler.isCommandBuilderUseful())
{
printCommandBuilder(getCommandBuilder(handler));
}
// Success or cancel.
app.println();
app.pressReturnToContinue();
return MenuResult.again();
}
} catch (ArgumentException e) {
app.println(e.getMessageObject());
return MenuResult.success(1);
} catch (ClientException e) {
app.println(e.getMessageObject());
return MenuResult.success(e.getExitCode());
}
}
}
/**
* The interactive mode sub-menu implementation.
*/
private class SubMenuCallback implements MenuCallback<Integer> {
// The menu.
private final Menu<Integer> menu;
/**
* Creates a new sub-menu implementation.
*
* @param app
* The console application.
* @param rd
* The relation definition.
* @param ch
* The optional create sub-command.
* @param dh
* The optional delete sub-command.
* @param lh
* The optional list sub-command.
* @param sh
* The option set-prop sub-command.
*/
public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd,
CreateSubCommandHandler<?, ?> ch, DeleteSubCommandHandler dh,
ListSubCommandHandler lh, SetPropSubCommandHandler sh) {
Message ufn = rd.getUserFriendlyName();
Message ufpn = null;
if (rd instanceof InstantiableRelationDefinition<?,?>) {
InstantiableRelationDefinition<?, ?> ir =
(InstantiableRelationDefinition<?, ?>) rd;
ufpn = ir.getUserFriendlyPluralName();
} else if (rd instanceof SetRelationDefinition<?,?>) {
SetRelationDefinition<?, ?> sr =
(SetRelationDefinition<?, ?>) rd;
ufpn = sr.getUserFriendlyPluralName();
}
MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(ufn));
builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
if (lh != null) {
SubCommandHandlerMenuCallback callback =
new SubCommandHandlerMenuCallback(lh);
if (ufpn != null) {
builder.addNumberedOption(
INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL.get(ufpn), callback);
} else {
builder
.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR
.get(ufn), callback);
}
}
if (ch != null) {
SubCommandHandlerMenuCallback callback =
new SubCommandHandlerMenuCallback(ch);
builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE
.get(ufn), callback);
}
if (sh != null) {
SubCommandHandlerMenuCallback callback =
new SubCommandHandlerMenuCallback(sh);
if (ufpn != null) {
builder
.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL
.get(ufn), callback);
} else {
builder.addNumberedOption(
INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR.get(ufn),
callback);
}
}
if (dh != null) {
SubCommandHandlerMenuCallback callback =
new SubCommandHandlerMenuCallback(dh);
builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE
.get(ufn), callback);
}
builder.addBackOption(true);
builder.addQuitOption();
this.menu = builder.toMenu();
}
/**
* {@inheritDoc}
*/
@Override
public final MenuResult<Integer> invoke(ConsoleApplication app)
throws CLIException {
try {
app.println();
app.println();
MenuResult<Integer> result = menu.run();
if (result.isCancel()) {
return MenuResult.again();
}
return result;
} catch (CLIException e) {
app.println(e.getMessageObject());
return MenuResult.success(1);
}
}
}
/**
* The type name which will be used for the most generic managed
* object types when they are instantiable and intended for
* customization only.
*/
public static final String CUSTOM_TYPE = "custom";
/**
* The type name which will be used for the most generic managed
* object types when they are instantiable and not intended for
* customization.
*/
public static final String GENERIC_TYPE = "generic";
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// This CLI is always using the administration connector with SSL
private static final boolean alwaysSSL = true;
private long sessionStartTime;
private boolean sessionStartTimePrinted = false;
private int sessionEquivalentOperationNumber = 0;
/**
* 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) {
int exitCode = main(args, true, System.out, System.err);
if (exitCode != 0) {
System.exit(filterExitCode(exitCode));
}
}
/**
* Provides the command-line arguments to the main application for
* processing and returns the exit code as an integer.
*
* @param args
* The set of command-line arguments provided to this
* program.
* @param initializeServer
* Indicates whether to perform basic initialization (which
* should not be done if the tool is running in the same
* JVM as the server).
* @param outStream
* The output stream for standard output.
* @param errStream
* The output stream for standard error.
* @return Zero to indicate that the program completed successfully,
* or non-zero to indicate that an error occurred.
*/
public static int main(String[] args, boolean initializeServer,
OutputStream outStream, OutputStream errStream) {
DSConfig app = new DSConfig(System.in, outStream, errStream,
new LDAPManagementContextFactory(alwaysSSL));
app.sessionStartTime = System.currentTimeMillis();
// Only initialize the client environment when run as a standalone
// application.
if (initializeServer) {
try {
app.initializeClientEnvironment();
} catch (InitializationException e) {
// TODO: is this ok as an error message?
app.println(e.getMessageObject());
return 1;
}
}
// Run the application.
return app.run(args);
}
// The argument which should be used to request advanced mode.
private BooleanArgument advancedModeArgument;
// Flag indicating whether or not the application environment has
// already been initialized.
private boolean environmentInitialized = false;
// 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;
// The sub-command handler factory.
private SubCommandHandlerFactory handlerFactory = null;
// Mapping of sub-commands to their implementations;
private final Map<SubCommand, SubCommandHandler> handlers =
new HashMap<SubCommand, SubCommandHandler>();
// Indicates whether or not a sub-command was provided.
private boolean hasSubCommand = true;
// The argument which should be used to read dsconfig commands from a file.
private StringArgument batchFileArgument;
// The argument which should be used to request non interactive
// behavior.
private BooleanArgument noPromptArgument;
// The argument that the user must set to display the equivalent
// non-interactive mode argument
private BooleanArgument displayEquivalentArgument;
// The argument that allows the user to dump the equivalent non-interactive
// command to a file.
private StringArgument equivalentCommandFileArgument;
// 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;
// The argument which should be used to request verbose output.
private BooleanArgument verboseArgument;
// The argument which should be used to indicate the properties file.
private StringArgument propertiesFileArgument;
// The argument which should be used to indicate that we will not look for
// properties file.
private BooleanArgument noPropertiesFileArgument;
/**
* 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.
*/
private DSConfig(InputStream in, OutputStream out, OutputStream err,
ManagementContextFactory factory) {
super(in, out, err);
this.parser = new SubCommandArgumentParser(this.getClass().getName(),
INFO_CONFIGDS_TOOL_DESCRIPTION.get(), false);
this.factory = factory;
}
/**
* Initializes core APIs for use when dsconfig will be run as a
* standalone application.
*
* @throws InitializationException
* If the core APIs could not be initialized.
*/
private void initializeClientEnvironment() throws InitializationException {
if (environmentInitialized == false) {
EmbeddedUtils.initializeForClientUse();
// 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;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAdvancedMode() {
return advancedModeArgument.isPresent();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isInteractive() {
return !noPromptArgument.isPresent();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMenuDrivenMode() {
return !hasSubCommand;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isQuiet() {
return quietArgument.isPresent();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isScriptFriendly() {
return scriptFriendlyArgument.isPresent();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isVerbose() {
return verboseArgument.isPresent();
}
// Displays the provided message followed by a help usage reference.
private void displayMessageAndUsageReference(Message message) {
println(message);
println();
println(parser.getHelpUsageReference());
}
/**
* Registers the global arguments with the argument parser.
*
* @throws ArgumentException
* If a global argument could not be registered.
*/
private void initializeGlobalArguments(String[] args)
throws ArgumentException {
if (globalArgumentsInitialized == false) {
verboseArgument = new BooleanArgument("verbose", 'v', "verbose",
INFO_DESCRIPTION_VERBOSE.get());
quietArgument = new BooleanArgument(
OPTION_LONG_QUIET,
OPTION_SHORT_QUIET,
OPTION_LONG_QUIET,
INFO_DESCRIPTION_QUIET.get());
quietArgument.setPropertyName(OPTION_LONG_QUIET);
scriptFriendlyArgument = new BooleanArgument("script-friendly",
OPTION_SHORT_SCRIPT_FRIENDLY, OPTION_LONG_SCRIPT_FRIENDLY,
INFO_DESCRIPTION_SCRIPT_FRIENDLY.get());
scriptFriendlyArgument.setPropertyName(OPTION_LONG_SCRIPT_FRIENDLY);
noPromptArgument = new BooleanArgument(
OPTION_LONG_NO_PROMPT,
OPTION_SHORT_NO_PROMPT,
OPTION_LONG_NO_PROMPT,
INFO_DESCRIPTION_NO_PROMPT.get());
advancedModeArgument = new BooleanArgument(OPTION_DSCFG_LONG_ADVANCED,
OPTION_DSCFG_SHORT_ADVANCED, OPTION_DSCFG_LONG_ADVANCED,
INFO_DSCFG_DESCRIPTION_ADVANCED.get());
advancedModeArgument.setPropertyName(OPTION_DSCFG_LONG_ADVANCED);
showUsageArgument = new BooleanArgument("showUsage", OPTION_SHORT_HELP,
OPTION_LONG_HELP, INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY
.get());
batchFileArgument = new StringArgument(OPTION_LONG_BATCH_FILE_PATH,
OPTION_SHORT_BATCH_FILE_PATH, OPTION_LONG_BATCH_FILE_PATH,
false, false, true, INFO_BATCH_FILE_PATH_PLACEHOLDER.get(),
null, null,
INFO_DESCRIPTION_BATCH_FILE_PATH.get());
displayEquivalentArgument = new BooleanArgument(
OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
null, OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get());
advancedModeArgument.setPropertyName(
OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT);
equivalentCommandFileArgument = new StringArgument(
OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, null,
OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, false, false, true,
INFO_PATH_PLACEHOLDER.get(), null, null,
INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get());
propertiesFileArgument = new StringArgument("propertiesFilePath",
null, OPTION_LONG_PROP_FILE_PATH,
false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
INFO_DESCRIPTION_PROP_FILE_PATH.get());
noPropertiesFileArgument = new BooleanArgument(
"noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
INFO_DESCRIPTION_NO_PROP_FILE.get());
// Register the global arguments.
ArgumentGroup toolOptionsGroup = new ArgumentGroup(
INFO_DESCRIPTION_CONFIG_OPTIONS_ARGS.get(), 2);
parser.addGlobalArgument(advancedModeArgument, toolOptionsGroup);
parser.addGlobalArgument(showUsageArgument);
parser.setUsageArgument(showUsageArgument, getOutputStream());
parser.addGlobalArgument(verboseArgument);
parser.addGlobalArgument(quietArgument);
parser.addGlobalArgument(scriptFriendlyArgument);
parser.addGlobalArgument(noPromptArgument);
parser.addGlobalArgument(batchFileArgument);
parser.addGlobalArgument(displayEquivalentArgument);
parser.addGlobalArgument(equivalentCommandFileArgument);
parser.addGlobalArgument(propertiesFileArgument);
parser.setFilePropertiesArgument(propertiesFileArgument);
parser.addGlobalArgument(noPropertiesFileArgument);
parser.setNoPropertiesFileArgument(noPropertiesFileArgument);
// Register any global arguments required by the management
// context factory.
factory.setRawArguments(args);
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 (handlerFactory == null) {
handlerFactory = new SubCommandHandlerFactory(parser);
Comparator<SubCommand> c = new Comparator<SubCommand>() {
@Override
public int compare(SubCommand o1, SubCommand o2) {
return o1.getName().compareTo(o2.getName());
}
};
Map<Tag, SortedSet<SubCommand>> groups =
new TreeMap<Tag, SortedSet<SubCommand>>();
SortedSet<SubCommand> allSubCommands = new TreeSet<SubCommand>(c);
for (SubCommandHandler handler : handlerFactory
.getAllSubCommandHandlers()) {
SubCommand sc = handler.getSubCommand();
handlers.put(sc, handler);
allSubCommands.add(sc);
// Add the sub-command to its groups.
for (Tag tag : handler.getTags()) {
SortedSet<SubCommand> group = groups.get(tag);
if (group == null) {
group = new TreeSet<SubCommand>(c);
groups.put(tag, group);
}
group.add(sc);
}
}
// Register the usage arguments.
for (Map.Entry<Tag, SortedSet<SubCommand>> group : groups.entrySet()) {
Tag tag = group.getKey();
SortedSet<SubCommand> subCommands = group.getValue();
String option = OPTION_LONG_HELP + "-" + tag.getName();
String synopsis = tag.getSynopsis().toString().toLowerCase();
BooleanArgument arg = new BooleanArgument(option, null, option,
INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE.get(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,
INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL.get());
parser.addGlobalArgument(arg);
parser.setUsageGroupArgument(arg, allSubCommands);
}
}
/**
* 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(args);
initializeSubCommands();
} catch (ArgumentException e) {
Message message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage());
println(message);
return 1;
}
// Parse the command-line arguments provided to this program.
try {
parser.parseArguments(args);
} catch (ArgumentException ae) {
Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
displayMessageAndUsageReference(message);
return 1;
}
// If the usage/version argument was provided, then we don't need
// to do anything else.
if (parser.usageOrVersionDisplayed()) {
return 0;
}
// Check for conflicting arguments.
if (quietArgument.isPresent() && verboseArgument.isPresent()) {
Message message = ERR_TOOL_CONFLICTING_ARGS.get(quietArgument
.getLongIdentifier(), verboseArgument.getLongIdentifier());
displayMessageAndUsageReference(message);
return 1;
}
if (batchFileArgument.isPresent() && !noPromptArgument.isPresent()) {
Message message =
ERR_DSCFG_ERROR_BATCH_FILE_AND_INTERACTIVE_INCOMPATIBLE.get(
batchFileArgument.getLongIdentifier(), noPromptArgument
.getLongIdentifier());
displayMessageAndUsageReference(message);
return 1;
}
if (quietArgument.isPresent() && !noPromptArgument.isPresent()) {
Message message = ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
quietArgument.getLongIdentifier(), noPromptArgument
.getLongIdentifier());
displayMessageAndUsageReference(message);
return 1;
}
if (scriptFriendlyArgument.isPresent() && verboseArgument.isPresent()) {
Message message = ERR_TOOL_CONFLICTING_ARGS.get(scriptFriendlyArgument
.getLongIdentifier(), verboseArgument.getLongIdentifier());
displayMessageAndUsageReference(message);
return 1;
}
if (noPropertiesFileArgument.isPresent()
&& propertiesFileArgument.isPresent())
{
Message message = ERR_TOOL_CONFLICTING_ARGS.get(
noPropertiesFileArgument.getLongIdentifier(),
propertiesFileArgument.getLongIdentifier());
displayMessageAndUsageReference(message);
return 1;
}
// Checks the version - if upgrade required, the tool is unusable
try
{
BuildVersion.checkVersionMismatch();
}
catch (InitializationException e)
{
println(e.getMessageObject());
return 1;
}
// Check that we can write on the provided path where we write the
// equivalent non-interactive commands.
if (equivalentCommandFileArgument.isPresent())
{
String file = equivalentCommandFileArgument.getValue();
if (!Utils.canWrite(file))
{
println(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file));
return 1;
}
else
{
File f = new File(file);
if (f.isDirectory())
{
println(ERR_DSCFG_EQUIVALENT_COMMAND_LINE_FILE_DIRECTORY.get(file));
return 1;
}
}
}
// Make sure that management context's arguments are valid.
try {
factory.validateGlobalArguments();
} catch (ArgumentException e) {
println(e.getMessageObject());
return 1;
}
// Handle batch file if any
if (batchFileArgument.isPresent()) {
handleBatchFile(args);
// don't need to do anything else
return 0;
}
int retCode = 0;
if (parser.getSubCommand() == null) {
hasSubCommand = false;
if (isInteractive()) {
// Top-level interactive mode.
retCode = runInteractiveMode();
} else {
Message message = ERR_ERROR_PARSING_ARGS
.get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
displayMessageAndUsageReference(message);
retCode = 1;
}
} else {
hasSubCommand = true;
// Retrieve the sub-command implementation and run it.
SubCommandHandler handler = handlers.get(parser.getSubCommand());
retCode = runSubCommand(handler);
}
try {
// Close the Management context ==> an LDAP UNBIND is sent
factory.close();
} catch (Exception e) {
// Nothing to report in this case
}
return retCode;
}
// Run the top-level interactive console.
private int runInteractiveMode() {
// In interactive mode, redirect all output to stdout.
ConsoleApplication app = new OutputStreamConsoleApplication(this);
// Build menu structure.
Comparator<RelationDefinition<?, ?>> c =
new Comparator<RelationDefinition<?, ?>>() {
@Override
public int compare(RelationDefinition<?, ?> rd1,
RelationDefinition<?, ?> rd2) {
String s1 = rd1.getUserFriendlyName().toString();
String s2 = rd2.getUserFriendlyName().toString();
return s1.compareToIgnoreCase(s2);
}
};
Set<RelationDefinition<?, ?>> relations;
Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers;
Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers;
Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers;
Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers;
Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers;
relations = new TreeSet<RelationDefinition<?, ?>>(c);
createHandlers =
new HashMap<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>>();
deleteHandlers =
new HashMap<RelationDefinition<?, ?>, DeleteSubCommandHandler>();
listHandlers =
new HashMap<RelationDefinition<?, ?>, ListSubCommandHandler>();
getPropHandlers =
new HashMap<RelationDefinition<?, ?>, GetPropSubCommandHandler>();
setPropHandlers =
new HashMap<RelationDefinition<?, ?>, SetPropSubCommandHandler>();
for (CreateSubCommandHandler<?, ?> ch : handlerFactory
.getCreateSubCommandHandlers()) {
relations.add(ch.getRelationDefinition());
createHandlers.put(ch.getRelationDefinition(), ch);
}
for (DeleteSubCommandHandler dh : handlerFactory
.getDeleteSubCommandHandlers()) {
relations.add(dh.getRelationDefinition());
deleteHandlers.put(dh.getRelationDefinition(), dh);
}
for (ListSubCommandHandler lh :
handlerFactory.getListSubCommandHandlers()) {
relations.add(lh.getRelationDefinition());
listHandlers.put(lh.getRelationDefinition(), lh);
}
for (GetPropSubCommandHandler gh : handlerFactory
.getGetPropSubCommandHandlers()) {
relations.add(gh.getRelationDefinition());
getPropHandlers.put(gh.getRelationDefinition(), gh);
}
for (SetPropSubCommandHandler sh : handlerFactory
.getSetPropSubCommandHandlers()) {
relations.add(sh.getRelationDefinition());
setPropHandlers.put(sh.getRelationDefinition(), sh);
}
// Main menu.
MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get());
builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get());
builder.setMultipleColumnThreshold(0);
for (RelationDefinition<?, ?> rd : relations) {
MenuCallback<Integer> callback = new SubMenuCallback(app, rd,
createHandlers.get(rd), deleteHandlers.get(rd), listHandlers.get(rd),
setPropHandlers.get(rd));
builder.addNumberedOption(rd.getUserFriendlyName(), callback);
}
builder.addQuitOption();
Menu<Integer> menu = builder.toMenu();
try {
// Force retrieval of management context.
factory.getManagementContext(app);
} catch (ArgumentException e) {
app.println(e.getMessageObject());
return 1;
} catch (ClientException e) {
app.println(e.getMessageObject());
return 1;
}
try {
app.println();
app.println();
MenuResult<Integer> result = menu.run();
if (result.isQuit()) {
return 0;
} else {
return result.getValue();
}
} catch (CLIException e) {
app.println(e.getMessageObject());
return 1;
}
}
// Run the provided sub-command handler.
private int runSubCommand(SubCommandHandler handler) {
try {
MenuResult<Integer> result = handler.run(this, factory);
if (result.isSuccess()) {
if (isInteractive() &&
handler.isCommandBuilderUseful())
{
printCommandBuilder(getCommandBuilder(handler));
}
return result.getValue();
} else {
// User must have quit.
return 1;
}
} catch (ArgumentException e) {
println(e.getMessageObject());
return 1;
} catch (CLIException e) {
println(e.getMessageObject());
return 1;
} catch (ClientException e) {
Throwable cause = e.getCause();
if (cause instanceof ManagedObjectDecodingException) {
ManagedObjectDecodingException de =
(ManagedObjectDecodingException) cause;
println();
displayManagedObjectDecodingException(this, de);
println();
} else if (cause instanceof MissingMandatoryPropertiesException) {
MissingMandatoryPropertiesException mmpe =
(MissingMandatoryPropertiesException) cause;
println();
displayMissingMandatoryPropertyException(this, mmpe);
println();
} else if (cause instanceof OperationRejectedException) {
OperationRejectedException ore = (OperationRejectedException) cause;
println();
displayOperationRejectedException(this, ore);
println();
} else {
// Just display the default message.
println(e.getMessageObject());
}
return 1;
} catch (Exception e) {
if (debugEnabled()) {
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
println(Message.raw(StaticUtils.stackTraceToString(e)));
return 1;
}
}
/**
* Updates the command builder with the global options: script friendly,
* verbose, etc. for a given subcommand. It also adds systematically the
* no-prompt option.
* @param handler the subcommand handler.
*/
private CommandBuilder getCommandBuilder(SubCommandHandler handler)
{
String commandName =
System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
if (commandName == null)
{
commandName = "dsconfig";
}
CommandBuilder commandBuilder =
new CommandBuilder(commandName, handler.getSubCommand().getName());
commandBuilder.append(handler.getCommandBuilder());
if ((factory != null) && (factory.getContextCommandBuilder() != null))
{
commandBuilder.append(factory.getContextCommandBuilder());
}
if (verboseArgument.isPresent())
{
commandBuilder.addArgument(verboseArgument);
}
if (scriptFriendlyArgument.isPresent())
{
commandBuilder.addArgument(scriptFriendlyArgument);
}
commandBuilder.addArgument(noPromptArgument);
if (propertiesFileArgument.isPresent())
{
commandBuilder.addArgument(propertiesFileArgument);
}
if (noPropertiesFileArgument.isPresent())
{
commandBuilder.addArgument(noPropertiesFileArgument);
}
return commandBuilder;
}
/**
* Creates a command builder with the global options: script friendly,
* verbose, etc. for a given subcommand name. It also adds systematically the
* no-prompt option.
* @param subcommandName the subcommand name.
* @return the command builder that has been created with the specified
* subcommandName.
*/
CommandBuilder getCommandBuilder(String subcommandName)
{
String commandName =
System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
if (commandName == null)
{
commandName = "dsconfig";
}
CommandBuilder commandBuilder =
new CommandBuilder(commandName, subcommandName);
if ((factory != null) && (factory.getContextCommandBuilder() != null))
{
commandBuilder.append(factory.getContextCommandBuilder());
}
if (verboseArgument.isPresent())
{
commandBuilder.addArgument(verboseArgument);
}
if (scriptFriendlyArgument.isPresent())
{
commandBuilder.addArgument(scriptFriendlyArgument);
}
commandBuilder.addArgument(noPromptArgument);
if (propertiesFileArgument.isPresent())
{
commandBuilder.addArgument(propertiesFileArgument);
}
if (noPropertiesFileArgument.isPresent())
{
commandBuilder.addArgument(noPropertiesFileArgument);
}
return commandBuilder;
}
/**
* Prints the contents of a command builder. This method has been created
* since SetPropSubCommandHandler calls it. All the logic of DSConfig is on
* this method. It writes the content of the CommandBuilder to the standard
* output, or to a file depending on the options provided by the user.
* @param commandBuilder the command builder to be printed.
*/
void printCommandBuilder(CommandBuilder commandBuilder)
{
if (displayEquivalentArgument.isPresent())
{
println();
// We assume that the app we are running is this one.
println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder.toString()));
}
if (equivalentCommandFileArgument.isPresent())
{
String file = equivalentCommandFileArgument.getValue();
try
{
BufferedWriter writer =
new BufferedWriter(new FileWriter(file, true));
if (!sessionStartTimePrinted)
{
writer.write(SHELL_COMMENT_SEPARATOR+getSessionStartTimeMessage());
writer.newLine();
sessionStartTimePrinted = true;
}
sessionEquivalentOperationNumber++;
writer.newLine();
writer.write(SHELL_COMMENT_SEPARATOR+
INFO_DSCFG_EQUIVALENT_COMMAND_LINE_SESSION_OPERATION_NUMBER.get(
sessionEquivalentOperationNumber));
writer.newLine();
writer.write(SHELL_COMMENT_SEPARATOR+getCurrentOperationDateMessage());
writer.newLine();
writer.write(commandBuilder.toString());
writer.newLine();
writer.newLine();
writer.flush();
writer.close();
}
catch (IOException ioe)
{
println(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file,
ioe.toString()));
}
}
}
/**
* Returns the message to be displayed in the file with the equivalent
* command-line with information about when the session started.
* @return the message to be displayed in the file with the equivalent
* command-line with information about when the session started.
*/
private String getSessionStartTimeMessage()
{
String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
if ((scriptName == null) || (scriptName.length() == 0))
{
scriptName = "dsconfig";
}
String date = formatDateTimeStringForEquivalentCommand(
new Date(sessionStartTime));
return INFO_DSCFG_SESSION_START_TIME_MESSAGE.get(scriptName, date).
toString();
}
private void handleBatchFile(String[] args) {
try {
// Build a list of initial arguments,
// removing the batch file option + its value
List<String> initialArgs = new ArrayList<String>();
Collections.addAll(initialArgs, args);
int batchFileArgIndex = -1;
for (String elem : initialArgs) {
if (elem.startsWith("-" + OPTION_SHORT_BATCH_FILE_PATH)
|| elem.contains(OPTION_LONG_BATCH_FILE_PATH)) {
batchFileArgIndex = initialArgs.indexOf(elem);
break;
}
}
if (batchFileArgIndex != -1) {
// Remove both the batch file arg and its value
initialArgs.remove(batchFileArgIndex);
initialArgs.remove(batchFileArgIndex);
}
String batchFilePath = batchFileArgument.getValue().trim();
BufferedReader reader =
new BufferedReader(new FileReader(batchFilePath));
String line;
String command = "";
//
// Split the CLI string into arguments array
//
while ((line = reader.readLine()) != null) {
if (line.equals("") || line.startsWith("#")) {
// Empty line or comment
continue;
}
// command split in several line support
if (line.endsWith("\\")) {
// command is split into several lines
command += line.substring(0, line.length() - 1);
continue;
} else {
command += line;
}
command = command.trim();
// string between quotes support
command = replaceSpacesInQuotes(command);
String displayCommand = new String(command);
// "\ " support
command = command.replace("\\ ", "##");
displayCommand = displayCommand.replace("\\ ", " ");
String[] fileArguments = command.split("\\s+");
// reset command
command = "";
for (int ii = 0; ii < fileArguments.length; ii++) {
fileArguments[ii] = fileArguments[ii].replace("##", " ");
}
printlnBatchProgress(displayCommand);
// Append initial arguments to the file line
List<String> allArguments = new ArrayList<String>();
Collections.addAll(allArguments, fileArguments);
allArguments.addAll(initialArgs);
String[] allArgsArray = allArguments.toArray(new String[]{});
int exitCode =
main(allArgsArray, false, getOutputStream(), getErrorStream());
if (exitCode != 0) {
reader.close();
System.exit(filterExitCode(exitCode));
}
printlnBatchProgress("");
}
reader.close();
} catch (IOException ex) {
println(ERR_DSCFG_ERROR_READING_BATCH_FILE.get(ex.toString()));
}
}
// Replace spaces in quotes by "\ "
private String replaceSpacesInQuotes(String line) {
String newLine = "";
boolean inQuotes = false;
for (int ii = 0; ii < line.length(); ii++) {
char ch = line.charAt(ii);
if ((ch == '\"') || (ch == '\'')) {
if (!inQuotes) {
// enter in a quoted string
inQuotes = true;
} else {
// end of a quoted string
inQuotes = false;
}
continue;
}
if (inQuotes && (ch == ' ')) {
newLine += "\\ ";
} else {
newLine += ch;
}
}
return newLine;
}
}