/*
* 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
*
*
* Copyright 2007-2010 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.tools.dsconfig;
import static org.opends.messages.DSConfigMessages.*;
import static org.opends.messages.ToolMessages.*;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.opends.messages.Message;
import org.opends.server.admin.AbstractManagedObjectDefinition;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.ConfigurationClient;
import org.opends.server.admin.DefinitionDecodingException;
import org.opends.server.admin.DurationUnit;
import org.opends.server.admin.InstantiableRelationDefinition;
import org.opends.server.admin.ManagedObjectDefinition;
import org.opends.server.admin.ManagedObjectNotFoundException;
import org.opends.server.admin.ManagedObjectOption;
import org.opends.server.admin.ManagedObjectPath;
import org.opends.server.admin.ManagedObjectPathSerializer;
import org.opends.server.admin.OptionalRelationDefinition;
import org.opends.server.admin.PropertyDefinition;
import org.opends.server.admin.PropertyDefinitionUsageBuilder;
import org.opends.server.admin.RelationDefinition;
import org.opends.server.admin.SetRelationDefinition;
import org.opends.server.admin.SingletonRelationDefinition;
import org.opends.server.admin.SizeUnit;
import org.opends.server.admin.Tag;
import org.opends.server.admin.client.AuthorizationException;
import org.opends.server.admin.client.CommunicationException;
import org.opends.server.admin.client.ConcurrentModificationException;
import org.opends.server.admin.client.IllegalManagedObjectNameException;
import org.opends.server.admin.client.ManagedObject;
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.tools.ClientException;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.args.Argument;
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.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.MenuResult;
import org.opends.server.util.table.TabSeparatedTablePrinter;
import org.opends.server.util.table.TablePrinter;
/**
* An interface for sub-command implementations.
*/
abstract class SubCommandHandler implements Comparable<SubCommandHandler> {
/**
* A path serializer which is used to retrieve a managed object
* based on a path and a list of path arguments.
*/
private class ManagedObjectFinder implements ManagedObjectPathSerializer {
// The console application.
private ConsoleApplication app;
// The index of the next path argument to be retrieved.
private int argIndex;
// The list of managed object path arguments.
private List<String> args;
private AuthorizationException authze;
private CommunicationException ce;
// Any CLI exception that was caught when attempting to find
// the managed object.
private CLIException clie;
private ConcurrentModificationException cme;
// Any operation exception that was caught when attempting to find
// the managed object.
private DefinitionDecodingException dde;
private ManagedObjectDecodingException mode;
private ManagedObjectNotFoundException monfe;
// The current result.
private MenuResult<ManagedObject<?>> result;
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
InstantiableRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d, String name) {
if (result.isSuccess()) {
// We should ignore the "template" name here and use a path
// argument.
String childName = args.get(argIndex++);
try {
// If the name is null then we must be interactive - so let
// the user choose.
if (childName == null) {
try {
MenuResult<String> sresult = readChildName(app,
result.getValue(), r, d);
if (sresult.isCancel()) {
result = MenuResult.cancel();
return;
} else if (sresult.isQuit()) {
result = MenuResult.quit();
return;
} else {
childName = sresult.getValue();
}
} catch (CLIException e) {
clie = e;
result = MenuResult.quit();
return;
}
} else if (childName.trim().length() == 0) {
IllegalManagedObjectNameException e =
new IllegalManagedObjectNameException(childName);
clie = ArgumentExceptionFactory
.adaptIllegalManagedObjectNameException(e, d);
result = MenuResult.quit();
return;
}
ManagedObject<?> child = result.getValue().getChild(r, childName);
// Check that child is a sub-type of the specified
// definition.
if (!child.getManagedObjectDefinition().isChildOf(d)) {
clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
.getManagedObjectDefinition(), getSubCommand().getName());
result = MenuResult.quit();
} else {
result = MenuResult.<ManagedObject<?>>success(child);
}
} catch (DefinitionDecodingException e) {
dde = e;
result = MenuResult.quit();
} catch (ManagedObjectDecodingException e) {
mode = e;
result = MenuResult.quit();
} catch (AuthorizationException e) {
authze = e;
result = MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
monfe = e;
result = MenuResult.quit();
} catch (ConcurrentModificationException e) {
cme = e;
result = MenuResult.quit();
} catch (CommunicationException e) {
ce = e;
result = MenuResult.quit();
}
}
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
OptionalRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
if (result.isSuccess()) {
try {
ManagedObject<?> child = result.getValue().getChild(r);
// Check that child is a sub-type of the specified
// definition.
if (!child.getManagedObjectDefinition().isChildOf(d)) {
clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
.getManagedObjectDefinition(), getSubCommand().getName());
result = MenuResult.quit();
} else {
result = MenuResult.<ManagedObject<?>>success(child);
}
} catch (DefinitionDecodingException e) {
dde = e;
result = MenuResult.quit();
} catch (ManagedObjectDecodingException e) {
mode = e;
result = MenuResult.quit();
} catch (AuthorizationException e) {
authze = e;
result = MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
monfe = e;
result = MenuResult.quit();
} catch (ConcurrentModificationException e) {
cme = e;
result = MenuResult.quit();
} catch (CommunicationException e) {
ce = e;
result = MenuResult.quit();
}
}
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
SetRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
if (result.isSuccess()) {
// We should ignore the "template" name here and use a path
// argument.
String childName = args.get(argIndex++);
try {
// If the name is null then we must be interactive - so let
// the user choose.
if (childName == null) {
try {
MenuResult<String> sresult = readChildName(app,
result.getValue(), r, d);
if (sresult.isCancel()) {
result = MenuResult.cancel();
return;
} else if (sresult.isQuit()) {
result = MenuResult.quit();
return;
} else {
childName = sresult.getValue();
}
} catch (CLIException e) {
clie = e;
result = MenuResult.quit();
return;
}
} else if (childName.trim().length() == 0) {
IllegalManagedObjectNameException e =
new IllegalManagedObjectNameException(childName);
clie = ArgumentExceptionFactory
.adaptIllegalManagedObjectNameException(e, d);
result = MenuResult.quit();
return;
} else {
String name = childName.trim();
SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>>
types = getSubTypes(d);
ManagedObjectDefinition<?, ?> cd = types.get(name);
if (cd == null) {
// The name must be invalid.
String typeUsage = getSubTypesUsage(d);
Message msg = ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED.get(
name, r.getUserFriendlyName(), typeUsage);
clie = new CLIException(msg);
result = MenuResult.quit();
return;
} else {
childName = cd.getName();
}
}
ManagedObject<?> child = result.getValue().getChild(r, childName);
// Check that child is a sub-type of the specified
// definition.
if (!child.getManagedObjectDefinition().isChildOf(d)) {
clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
.getManagedObjectDefinition(), getSubCommand().getName());
result = MenuResult.quit();
} else {
result = MenuResult.<ManagedObject<?>>success(child);
}
} catch (DefinitionDecodingException e) {
dde = e;
result = MenuResult.quit();
} catch (ManagedObjectDecodingException e) {
mode = e;
result = MenuResult.quit();
} catch (AuthorizationException e) {
authze = e;
result = MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
monfe = e;
result = MenuResult.quit();
} catch (ConcurrentModificationException e) {
cme = e;
result = MenuResult.quit();
} catch (CommunicationException e) {
ce = e;
result = MenuResult.quit();
}
}
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
SingletonRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
if (result.isSuccess()) {
try {
ManagedObject<?> child = result.getValue().getChild(r);
// Check that child is a sub-type of the specified
// definition.
if (!child.getManagedObjectDefinition().isChildOf(d)) {
clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
.getManagedObjectDefinition(), getSubCommand().getName());
result = MenuResult.quit();
} else {
result = MenuResult.<ManagedObject<?>>success(child);
}
} catch (DefinitionDecodingException e) {
dde = e;
result = MenuResult.quit();
} catch (ManagedObjectDecodingException e) {
mode = e;
result = MenuResult.quit();
} catch (AuthorizationException e) {
authze = e;
result = MenuResult.quit();
} catch (ManagedObjectNotFoundException e) {
monfe = e;
result = MenuResult.quit();
} catch (ConcurrentModificationException e) {
cme = e;
result = MenuResult.quit();
} catch (CommunicationException e) {
ce = e;
result = MenuResult.quit();
}
}
}
/**
* Finds the named managed object.
*
* @param app
* The console application.
* @param context
* The management context.
* @param path
* The managed object path.
* @param args
* The managed object path arguments.
* @return Returns a {@link MenuResult#success()} containing the
* managed object referenced by the provided managed
* object path, or {@link MenuResult#quit()}, or
* {@link MenuResult#cancel()}, if the sub-command was
* run interactively and the user chose to quit or cancel.
* @throws CLIException
* If one of the naming arguments referenced a managed
* object of the wrong type.
* @throws DefinitionDecodingException
* If the managed object was found but its type could
* not be determined.
* @throws ManagedObjectDecodingException
* If the managed object was found but one or more of
* its properties could not be decoded.
* @throws ManagedObjectNotFoundException
* If the requested managed object could not be found on
* the server.
* @throws ConcurrentModificationException
* If this managed object has been removed from the
* server by another client.
* @throws AuthorizationException
* If the server refuses to retrieve the managed object
* because the client does not have the correct
* privileges.
* @throws CommunicationException
* If the client cannot contact the server due to an
* underlying communication problem.
*/
public MenuResult<ManagedObject<?>> find(ConsoleApplication app,
ManagementContext context, ManagedObjectPath<?, ?> path,
List<String> args) throws CLIException, CommunicationException,
AuthorizationException, ConcurrentModificationException,
DefinitionDecodingException, ManagedObjectDecodingException,
ManagedObjectNotFoundException {
this.result = MenuResult.<ManagedObject<?>> success(context
.getRootConfigurationManagedObject());
this.app = app;
this.args = args;
this.argIndex = 0;
this.clie = null;
this.authze = null;
this.ce = null;
this.cme = null;
this.dde = null;
this.mode = null;
this.monfe = null;
path.serialize(this);
if (result.isSuccess()) {
return result;
} else if (clie != null) {
throw clie;
} else if (authze != null) {
throw authze;
} else if (ce != null) {
throw ce;
} else if (cme != null) {
throw cme;
} else if (dde != null) {
throw dde;
} else if (mode != null) {
throw mode;
} else if (monfe != null) {
throw monfe;
} else {
// User requested termination interactively.
return result;
}
}
}
/**
* A path serializer which is used to register a sub-command's
* naming arguments.
*/
protected static class NamingArgumentBuilder implements
ManagedObjectPathSerializer {
/**
* Creates the naming arguments for a given path.
*
* @param subCommand
* The sub-command.
* @param path
* The managed object path.
* @param isCreate
* Indicates whether the sub-command is a create-xxx
* sub-command, in which case the final path element will
* have different usage information.
* @return Returns the naming arguments.
* @throws ArgumentException
* If one or more naming arguments could not be
* registered.
*/
public static List<StringArgument> create(SubCommand subCommand,
ManagedObjectPath<?, ?> path, boolean isCreate)
throws ArgumentException {
NamingArgumentBuilder builder = new NamingArgumentBuilder(subCommand,
path.size(), isCreate);
path.serialize(builder);
if (builder.e != null) {
throw builder.e;
}
return builder.arguments;
}
// The list of naming arguments.
private final List<StringArgument> arguments =
new LinkedList<StringArgument>();
// Any argument exception thrown when creating the naming
// arguments.
private ArgumentException e = null;
// Indicates whether the sub-command is a create-xxx
// sub-command, in which case the final path element will
// have different usage information.
private final boolean isCreate;
// The sub-command.
private final SubCommand subCommand;
// The number of path elements to expect.
private int sz;
// Private constructor.
private NamingArgumentBuilder(SubCommand subCommand, int sz,
boolean isCreate) {
this.subCommand = subCommand;
this.sz = sz;
this.isCreate = isCreate;
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
InstantiableRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d, String name) {
sz--;
String argName = CLIProfile.getInstance().getNamingArgument(r);
StringArgument arg;
try {
if (isCreate && sz == 0) {
// The final path element in create-xxx sub-commands should
// have a different usage.
PropertyDefinition<?> pd = r.getNamingPropertyDefinition();
if (pd != null) {
// Use syntax and description from naming property.
PropertyDefinitionUsageBuilder b =
new PropertyDefinitionUsageBuilder(false);
Message usage = Message.raw("{" + b.getUsage(pd) + "}");
arg = new StringArgument(argName, null, argName, false, true,
usage, INFO_DSCFG_DESCRIPTION_NAME_CREATE_EXT.get(d
.getUserFriendlyName(), pd.getName(), pd.getSynopsis()));
} else {
arg = new StringArgument(argName, null, argName, false, true,
INFO_NAME_PLACEHOLDER.get(),
INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(
d.getUserFriendlyName()));
}
} else {
// A normal naming argument.
arg = new StringArgument(argName, null, argName, false, true,
INFO_NAME_PLACEHOLDER.get(), INFO_DSCFG_DESCRIPTION_NAME.get(
d.getUserFriendlyName()));
}
subCommand.addArgument(arg);
arguments.add(arg);
} catch (ArgumentException e) {
this.e = e;
}
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
OptionalRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
sz--;
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
SetRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
sz--;
// The name of the managed object is determined from its type, so
// we don't need this argument.
if (isCreate && sz == 0) {
return;
}
String argName = CLIProfile.getInstance().getNamingArgument(r);
StringArgument arg;
try {
arg =
new StringArgument(argName, null, argName, false, true,
INFO_NAME_PLACEHOLDER.get(),
INFO_DSCFG_DESCRIPTION_NAME.get(d.getUserFriendlyName()));
subCommand.addArgument(arg);
arguments.add(arg);
} catch (ArgumentException e) {
this.e = e;
}
}
/**
* {@inheritDoc}
*/
public <C extends ConfigurationClient, S extends Configuration>
void appendManagedObjectPathElement(
SingletonRelationDefinition<? super C, ? super S> r,
AbstractManagedObjectDefinition<C, S> d) {
sz--;
}
}
/**
* The threshold above which choice menus should be displayed in
* multiple columns.
*/
public static final int MULTI_COLUMN_THRESHOLD = 8;
/**
* The value for the long option property.
*/
private static final String OPTION_DSCFG_LONG_PROPERTY = "property";
/**
* The value for the long option record.
*/
private static final String OPTION_DSCFG_LONG_RECORD = "record";
/**
* The value for the long option unit-size.
*/
private static final String OPTION_DSCFG_LONG_UNIT_SIZE = "unit-size";
/**
* The value for the long option unit-time.
*/
private static final String OPTION_DSCFG_LONG_UNIT_TIME = "unit-time";
/**
* The value for the short option property.
*/
private static final Character OPTION_DSCFG_SHORT_PROPERTY = null;
/**
* The value for the short option record.
*/
private static final char OPTION_DSCFG_SHORT_RECORD = 'E';
/**
* The value for the short option unit-size.
*/
private static final char OPTION_DSCFG_SHORT_UNIT_SIZE = 'z';
/**
* The value for the short option unit-time.
*/
private static final char OPTION_DSCFG_SHORT_UNIT_TIME = 'm';
// The argument which should be used to specify zero or more
// property names.
private StringArgument propertyArgument;
// The argument which should be used to request record mode.
private BooleanArgument recordModeArgument;
// The tags associated with this sub-command handler.
private final Set<Tag> tags = new HashSet<Tag>();
// The argument which should be used to request specific size units.
private StringArgument unitSizeArgument;
// The argument which should be used to request specific time units.
private StringArgument unitTimeArgument;
// The command builder associated with this handler.
private CommandBuilder commandBuilder;
/**
* The boolean that says whether is useful to display the command builder's
* contents after calling the run method or not.
*/
private boolean isCommandBuilderUseful = true;
/**
* Create a new sub-command handler.
*/
protected SubCommandHandler() {
// No implementation required.
}
/**
* {@inheritDoc}
*/
public final int compareTo(SubCommandHandler o) {
String s1 = getSubCommand().getName();
String s2 = o.getSubCommand().getName();
return s1.compareTo(s2);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof SubCommandHandler) {
SubCommandHandler other = (SubCommandHandler) obj;
String s1 = getSubCommand().getName();
String s2 = other.getSubCommand().getName();
return s1.equals(s2);
} else {
return false;
}
}
/**
* Gets the sub-command associated with this handler.
*
* @return Returns the sub-command associated with this handler.
*/
public abstract SubCommand getSubCommand();
/**
* Gets the command builder associated with this handler. The method should
* be called after calling <CODE>run()</CODE> method.
*
* @return Returns the sub-command associated with this handler.
*/
public final CommandBuilder getCommandBuilder()
{
if (commandBuilder == null)
{
commandBuilder = new CommandBuilder(
System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME),
getSubCommand().getName());
}
return commandBuilder;
}
/**
* This method tells whether displaying the command builder contents makes
* sense or not. For instance in the case of the help subcommand handler
* displaying information makes no much sense.
*
* @return <CODE>true</CODE> if displaying the command builder is useful and
* <CODE>false</CODE> otherwise.
*/
public final boolean isCommandBuilderUseful()
{
return isCommandBuilderUseful;
}
/**
* Sets wheter the command builder is useful or not.
* @param isCommandBuilderUseful whether the command builder is useful or not.
*/
protected final void setCommandBuilderUseful(boolean isCommandBuilderUseful)
{
this.isCommandBuilderUseful = isCommandBuilderUseful;
}
/**
* Gets the tags associated with this sub-command handler.
*
* @return Returns the tags associated with this sub-command
* handler.
*/
public final Set<Tag> getTags() {
return tags;
}
/**
* {@inheritDoc}
*/
@Override
public final int hashCode() {
return getSubCommand().getName().hashCode();
}
/**
* Run this sub-command handler.
*
* @param app
* The console application.
* @param factory
* The management context factory.
* @return Returns a {@link MenuResult#success()} containing zero if
* the sub-command completed successfully or non-zero if it
* did not, or {@link MenuResult#quit()}, or
* {@link MenuResult#cancel()}, if the sub-command was run
* interactively and the user chose to quit or cancel.
* @throws ArgumentException
* If an argument required by the sub-command could not be
* parsed successfully.
* @throws ClientException
* If the management context could not be created.
* @throws CLIException
* If a CLI exception occurred.
*/
public abstract MenuResult<Integer> run(ConsoleApplication app,
ManagementContextFactory factory) throws ArgumentException,
ClientException, CLIException;
/**
* Get the string representation of this sub-command handler.
* <p>
* The string representation is simply the sub-command's name.
*
* @return Returns the string representation of this sub-command
* handler.
*/
@Override
public final String toString() {
return getSubCommand().getName();
}
/**
* Adds one or more tags to this sub-command handler.
*
* @param tags
* The tags to be added to this sub-command handler.
*/
protected final void addTags(Collection<Tag> tags) {
this.tags.addAll(tags);
}
/**
* Adds one or more tags to this sub-command handler.
*
* @param tags
* The tags to be added to this sub-command handler.
*/
protected final void addTags(Tag... tags) {
addTags(Arrays.asList(tags));
}
/**
* Creates the naming arguments for a given path and registers them.
*
* @param subCommand
* The sub-command.
* @param p
* The managed object path.
* @param isCreate
* Indicates whether the sub-command is a create-xxx
* sub-command, in which case the final path element will
* have different usage information.
* @return Returns the naming arguments.
* @throws ArgumentException
* If one or more naming arguments could not be
* registered.
*/
protected final List<StringArgument> createNamingArgs(SubCommand subCommand,
ManagedObjectPath<?, ?> p, boolean isCreate) throws ArgumentException {
return NamingArgumentBuilder.create(subCommand, p, isCreate);
}
/**
* Creates a script-friendly table printer. This factory method
* should be used by sub-command handler implementations rather than
* constructing a table printer directly so that we can easily
* switch table implementations (perhaps dynamically depending on
* argument).
*
* @param stream
* The output stream for the table.
* @return Returns a script-friendly table printer.
*/
protected final TablePrinter createScriptFriendlyTablePrinter(
PrintStream stream) {
return new TabSeparatedTablePrinter(stream);
}
/**
* Get the managed object referenced by the provided managed object
* path.
*
* @param app
* The console application.
* @param context
* The management context.
* @param path
* The managed object path.
* @param args
* The list of managed object names required by the path.
* @return Returns a {@link MenuResult#success()} containing the
* managed object referenced by the provided managed object
* path, or {@link MenuResult#quit()}, or
* {@link MenuResult#cancel()}, if the sub-command was run
* interactively and the user chose to quit or cancel.
* @throws DefinitionDecodingException
* If the managed object was found but its type could not
* be determined.
* @throws ManagedObjectDecodingException
* If the managed object was found but one or more of its
* properties could not be decoded.
* @throws ManagedObjectNotFoundException
* If the requested managed object could not be found on
* the server.
* @throws ConcurrentModificationException
* If this managed object has been removed from the server
* by another client.
* @throws AuthorizationException
* If the server refuses to retrieve the managed object
* because the client does not have the correct
* privileges.
* @throws CommunicationException
* If the client cannot contact the server due to an
* underlying communication problem.
* @throws CLIException
* If one of the naming arguments referenced a managed
* object of the wrong type.
* @throws ClientException
* If the management context could not be created.
*/
protected final MenuResult<ManagedObject<?>> getManagedObject(
ConsoleApplication app, ManagementContext context,
ManagedObjectPath<?, ?> path, List<String> args) throws CLIException,
AuthorizationException, DefinitionDecodingException,
ManagedObjectDecodingException, CommunicationException,
ConcurrentModificationException, ManagedObjectNotFoundException,
ClientException {
ManagedObjectFinder finder = new ManagedObjectFinder();
return finder.find(app, context, path, args);
}
/**
* Gets the values of the naming arguments.
*
* @param app
* The console application.
* @param namingArgs
* The naming arguments.
* @return Returns the values of the naming arguments.
* @throws ArgumentException
* If one of the naming arguments is missing and the
* application is non-interactive.
*/
protected final List<String> getNamingArgValues(ConsoleApplication app,
List<StringArgument> namingArgs) throws ArgumentException {
ArrayList<String> values = new ArrayList<String>(namingArgs.size());
for (StringArgument arg : namingArgs) {
String value = arg.getValue();
if (value == null && !app.isInteractive()) {
throw ArgumentExceptionFactory
.missingMandatoryNonInteractiveArgument(arg);
} else {
values.add(value);
}
}
return values;
}
/**
* Gets the optional list of property names that the user requested.
*
* @return Returns the optional list of property names that the user
* requested.
*/
protected final Set<String> getPropertyNames() {
if (propertyArgument != null) {
return new LinkedHashSet<String>(propertyArgument.getValues());
} else {
return Collections.emptySet();
}
}
/**
* Gets the optional size unit that the user requested.
*
* @return Returns the size unit that the user requested, or
* <code>null</code> if no size unit was specified.
* @throws ArgumentException
* If the user specified an invalid size unit.
*/
protected final SizeUnit getSizeUnit() throws ArgumentException {
if (unitSizeArgument != null) {
String value = unitSizeArgument.getValue();
if (value != null) {
try {
return SizeUnit.getUnit(value);
} catch (IllegalArgumentException e) {
Message msg = INFO_DSCFG_ERROR_SIZE_UNIT_UNRECOGNIZED.get(value);
throw new ArgumentException(msg);
}
}
}
return null;
}
/**
* Gets the optional time unit that the user requested.
*
* @return Returns the time unit that the user requested, or
* <code>null</code> if no time unit was specified.
* @throws ArgumentException
* If the user specified an invalid time unit.
*/
protected final DurationUnit getTimeUnit() throws ArgumentException {
if (unitTimeArgument != null) {
String value = unitTimeArgument.getValue();
if (value != null) {
try {
return DurationUnit.getUnit(value);
} catch (IllegalArgumentException e) {
Message msg = INFO_DSCFG_ERROR_TIME_UNIT_UNRECOGNIZED.get(value);
throw new ArgumentException(msg);
}
}
}
return null;
}
/**
* Determines whether the user requested record-mode.
*
* @return Returns <code>true</code> if the user requested
* record-mode.
*/
protected final boolean isRecordMode() {
if (recordModeArgument != null) {
return recordModeArgument.isPresent();
} else {
return false;
}
}
/**
* Interactively prompts the user to select from a choice of child
* managed objects.
* <p>
* This method will adapt according to the available choice. For
* example, if there is only one choice, then a question will be
* asked. If there are no children then an
* <code>ArgumentException</code> will be thrown.
*
* @param <C>
* The type of child client configuration.
* @param <S>
* The type of child server configuration.
* @param app
* The console application.
* @param parent
* The parent managed object.
* @param r
* The relation between the parent and the children, must be
* a set or instantiable relation.
* @param d
* The type of child managed object to choose from.
* @return Returns a {@link MenuResult#success()} containing the name
* of the managed object that the user selected, or
* {@link MenuResult#quit()}, or {@link MenuResult#cancel()},
* if the sub-command was run interactive and the user chose
* to quit or cancel.
* @throws CommunicationException
* If the server cannot be contacted.
* @throws ConcurrentModificationException
* If the parent managed object has been deleted.
* @throws AuthorizationException
* If the children cannot be listed due to an authorization
* failure.
* @throws CLIException
* If the user input can be read from the console or if
* there are no children.
*/
protected final <C extends ConfigurationClient, S extends Configuration>
MenuResult<String> readChildName(
ConsoleApplication app, ManagedObject<?> parent,
RelationDefinition<C, S> r,
AbstractManagedObjectDefinition<? extends C, ? extends S> d)
throws AuthorizationException, ConcurrentModificationException,
CommunicationException, CLIException {
if (d == null) {
d = r.getChildDefinition();
}
app.println();
app.println();
// Filter out advanced and hidden types if required.
String[] childNames;
if (r instanceof InstantiableRelationDefinition) {
childNames =
parent.listChildren((InstantiableRelationDefinition<C,S>)r, d);
} else {
childNames = parent.listChildren((SetRelationDefinition<C,S>)r, d);
}
SortedMap<String, String> children = new TreeMap<String, String>(
String.CASE_INSENSITIVE_ORDER);
for (String childName : childNames) {
ManagedObject<?> child;
try {
if (r instanceof InstantiableRelationDefinition) {
child = parent.getChild((InstantiableRelationDefinition<C,S>)r,
childName);
} else {
child = parent.getChild((SetRelationDefinition<C,S>)r, childName);
}
ManagedObjectDefinition<?, ?> cd = child.getManagedObjectDefinition();
if (cd.hasOption(ManagedObjectOption.HIDDEN)) {
continue;
}
if (!app.isAdvancedMode()
&& cd.hasOption(ManagedObjectOption.ADVANCED)) {
continue;
}
if (r instanceof InstantiableRelationDefinition) {
children.put(childName, childName);
} else {
// For sets the RDN is the type string, the ufn is more friendly.
children.put(cd.getUserFriendlyName().toString(), childName);
}
} catch (DefinitionDecodingException e) {
// Add it anyway: maybe the user is trying to fix the problem.
children.put(childName, childName);
} catch (ManagedObjectDecodingException e) {
// Add it anyway: maybe the user is trying to fix the problem.
children.put(childName, childName);
} catch (ManagedObjectNotFoundException e) {
// Skip it - the managed object has been concurrently removed.
}
}
switch (children.size()) {
case 0: {
// No options available - abort.
Message msg =
ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(d.getUserFriendlyPluralName());
app.println(msg);
return MenuResult.cancel();
}
case 1: {
// Only one option available so confirm that the user wishes to
// access it.
Message msg = INFO_DSCFG_FINDER_PROMPT_SINGLE.get(
d.getUserFriendlyName(), children.firstKey());
if (app.confirmAction(msg, true)) {
try
{
String argName = CLIProfile.getInstance().getNamingArgument(r);
StringArgument arg = new StringArgument(argName, null, argName, false,
true, INFO_NAME_PLACEHOLDER.get(),
INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
if (r instanceof InstantiableRelationDefinition) {
arg.addValue(children.get(children.firstKey()));
} else {
// Set relation: need the short type name.
String friendlyName = children.firstKey();
String shortName = children.get(friendlyName);
try
{
AbstractManagedObjectDefinition<?, ?> cd = null;
try
{
cd = d.getChild(shortName);
}
catch (IllegalArgumentException e)
{
// Last resource: try with friendly name
cd = d.getChild(friendlyName);
}
arg.addValue(getShortTypeName(r.getChildDefinition(), cd));
}
catch (IllegalArgumentException e)
{
arg.addValue(shortName);
}
}
getCommandBuilder().addArgument(arg);
}
catch (Throwable t)
{
// Bug
throw new RuntimeException("Unexpected exception: "+t, t);
}
return MenuResult.success(children.get(children.firstKey()));
} else {
return MenuResult.cancel();
}
}
default: {
// Display a menu.
MenuBuilder<String> builder = new MenuBuilder<String>(app);
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
builder.setPrompt(INFO_DSCFG_FINDER_PROMPT_MANY.get(d
.getUserFriendlyName()));
for (Map.Entry<String, String> child : children.entrySet()) {
Message option = Message.raw("%s", child.getKey());
builder.addNumberedOption(option, MenuResult.success(child.getValue()));
}
if (app.isMenuDrivenMode()) {
builder.addCancelOption(true);
}
builder.addQuitOption();
Menu<String> menu = builder.toMenu();
MenuResult<String> result = menu.run();
try
{
if (result.getValue() == null) {
// nothing has been entered ==> cancel
return MenuResult.cancel();
}
String argName = CLIProfile.getInstance().getNamingArgument(r);
StringArgument arg = new StringArgument(argName, null, argName, false,
true, INFO_NAME_PLACEHOLDER.get(),
INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
if (r instanceof InstantiableRelationDefinition) {
arg.addValue(result.getValue());
} else {
// Set relation: need the short type name.
String longName = result.getValue();
try {
AbstractManagedObjectDefinition<?, ?> cd = d.getChild(longName);
arg.addValue(getShortTypeName(r.getChildDefinition(), cd));
} catch (IllegalArgumentException e) {
arg.addValue(children.get(result.getValue()));
}
}
getCommandBuilder().addArgument(arg);
}
catch (Throwable t)
{
// Bug
throw new RuntimeException("Unexpected exception: "+t, t);
}
return result;
}
}
}
/**
* Registers the property name argument with the sub-command.
*
* @param subCommand
* The sub-command.
* @throws ArgumentException
* If the property name argument could not be registered.
*/
protected final void registerPropertyNameArgument(SubCommand subCommand)
throws ArgumentException {
this.propertyArgument = new StringArgument(OPTION_DSCFG_LONG_PROPERTY,
OPTION_DSCFG_SHORT_PROPERTY, OPTION_DSCFG_LONG_PROPERTY, false, true,
true, INFO_PROPERTY_PLACEHOLDER.get(), null, null,
INFO_DSCFG_DESCRIPTION_PROP.get());
subCommand.addArgument(propertyArgument);
}
/**
* Registers the record mode argument with the sub-command.
*
* @param subCommand
* The sub-command.
* @throws ArgumentException
* If the record mode argument could not be registered.
*/
protected final void registerRecordModeArgument(SubCommand subCommand)
throws ArgumentException {
this.recordModeArgument = new BooleanArgument(OPTION_DSCFG_LONG_RECORD,
OPTION_DSCFG_SHORT_RECORD, OPTION_DSCFG_LONG_RECORD,
INFO_DSCFG_DESCRIPTION_RECORD.get());
this.recordModeArgument.setPropertyName(OPTION_DSCFG_LONG_RECORD);
subCommand.addArgument(recordModeArgument);
}
/**
* Registers the unit-size argument with the sub-command.
*
* @param subCommand
* The sub-command.
* @throws ArgumentException
* If the unit-size argument could not be registered.
*/
protected final void registerUnitSizeArgument(SubCommand subCommand)
throws ArgumentException {
this.unitSizeArgument = new StringArgument(OPTION_DSCFG_LONG_UNIT_SIZE,
OPTION_DSCFG_SHORT_UNIT_SIZE, OPTION_DSCFG_LONG_UNIT_SIZE, false, true,
INFO_UNIT_PLACEHOLDER.get(), INFO_DSCFG_DESCRIPTION_UNIT_SIZE.get());
this.unitSizeArgument.setPropertyName(OPTION_DSCFG_LONG_UNIT_SIZE);
subCommand.addArgument(unitSizeArgument);
}
/**
* Registers the unit-time argument with the sub-command.
*
* @param subCommand
* The sub-command.
* @throws ArgumentException
* If the unit-time argument could not be registered.
*/
protected final void registerUnitTimeArgument(SubCommand subCommand)
throws ArgumentException {
this.unitTimeArgument = new StringArgument(OPTION_DSCFG_LONG_UNIT_TIME,
OPTION_DSCFG_SHORT_UNIT_TIME, OPTION_DSCFG_LONG_UNIT_TIME, false, true,
INFO_UNIT_PLACEHOLDER.get(), INFO_DSCFG_DESCRIPTION_UNIT_TIME.get());
this.unitTimeArgument.setPropertyName(OPTION_DSCFG_LONG_UNIT_TIME);
subCommand.addArgument(unitTimeArgument);
}
/**
* Updates the command builder with the arguments defined in the sub command.
* This implies basically putting the arguments provided by the user in the
* command builder.
*/
protected final void updateCommandBuilderWithSubCommand()
{
for (Argument arg : getSubCommand().getArguments())
{
if (arg.isPresent())
{
getCommandBuilder().addArgument(arg);
}
}
}
/**
* Returns the string value for a given object as it will be displayed
* in the equivalent command-line. The code will cast the provided object
* using the property definition.
* @param propertyDefinition the property definition.
* @param o the value.
* @param <T> the type of the property to be retrieved.
* @return the String value to be displayed in the equivalent command-line.
*/
protected static <T> String castAndGetArgumentValue(
PropertyDefinition<T> propertyDefinition, Object o)
{
String value = propertyDefinition.encodeValue(
propertyDefinition.castValue(o));
return value;
}
/**
* Returns the string value for a given object as it will be displayed
* in the equivalent command-line.
* @param propertyDefinition the property definition.
* @param o the value.
* @param <T> the type of the property to be retrieved.
* @return the String value to be displayed in the equivalent command-line.
*/
protected static <T> String getArgumentValue(
PropertyDefinition<T> propertyDefinition, T o)
{
String value;
value = propertyDefinition.encodeValue(o);
return value;
}
/**
* Returns a mapping of subordinate managed object type argument
* values to their corresponding managed object definitions for the
* provided managed object definition.
*
* @param <C>
* The type of client configuration.
* @param <S>
* The type of server configuration.
* @param d
* The managed object definition.
* @return A mapping of managed object type argument values to their
* corresponding managed object definitions.
*/
protected static <C extends ConfigurationClient, S extends Configuration>
SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>>
getSubTypes(AbstractManagedObjectDefinition<C, S> d) {
SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>> map;
map =
new TreeMap<String, ManagedObjectDefinition<? extends C, ? extends S>>();
// If the top-level definition is instantiable, we use the value
// "generic" or "custom".
if (!d.hasOption(ManagedObjectOption.HIDDEN)) {
if (d instanceof ManagedObjectDefinition) {
ManagedObjectDefinition<? extends C, ? extends S> mod =
(ManagedObjectDefinition<? extends C, ? extends S>) d;
map.put(getShortTypeName(d, mod), mod);
}
}
// Process its sub-definitions.
for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d
.getAllChildren()) {
if (c.hasOption(ManagedObjectOption.HIDDEN)) {
continue;
}
if (c instanceof ManagedObjectDefinition) {
@SuppressWarnings("unchecked")
ManagedObjectDefinition<? extends C, ? extends S> mod =
(ManagedObjectDefinition<? extends C, ? extends S>) c;
map.put(getShortTypeName(d, mod), mod);
}
}
return map;
}
/**
* Returns the type short name for a child managed object definition.
*
* @param <C>
* The type of client configuration.
* @param <S>
* The type of server configuration.
* @param d
* The top level parent definition.
* @param c
* The child definition.
* @return The type short name.
*/
protected static <C extends ConfigurationClient, S extends Configuration>
String getShortTypeName(
AbstractManagedObjectDefinition<C,S> d,
AbstractManagedObjectDefinition<?, ?> c) {
if (c == d) {
// This is the top-level definition, so use the value "generic" or
// "custom".
if (CLIProfile.getInstance().isForCustomization(c)) {
return DSConfig.CUSTOM_TYPE;
} else {
return DSConfig.GENERIC_TYPE;
}
} else {
// It's a child definition.
String suffix = "-" + d.getName();
// For the type name we shorten it, if possible, by stripping
// off the trailing part of the name which matches the
// base-type.
String name = c.getName();
if (name.endsWith(suffix)) {
name = name.substring(0, name.length() - suffix.length());
}
// If this type is intended for customization, prefix it with
// "custom".
if (CLIProfile.getInstance().isForCustomization(c)) {
name = String.format("%s-%s", DSConfig.CUSTOM_TYPE, name);
}
return name;
}
}
/**
* Returns a usage string representing the list of possible types for
* the provided managed object definition.
*
* @param d
* The managed object definition.
* @return A usage string representing the list of possible types for
* the provided managed object definition.
*/
protected static String getSubTypesUsage(
AbstractManagedObjectDefinition<?, ?> d) {
// Build the -t option usage.
SortedMap<String, ?> types = getSubTypes(d);
StringBuilder builder = new StringBuilder();
boolean isFirst = true;
for (String s : types.keySet()) {
if (!isFirst) {
builder.append(" | ");
}
builder.append(s);
isFirst = false;
}
return builder.toString();
}
}