/*
|
* 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 2008-2010 Sun Microsystems, Inc.
|
* Portions Copyright 2013-2014 ForgeRock AS
|
*/
|
package org.forgerock.opendj.config.dsconfig;
|
|
import static com.forgerock.opendj.cli.ReturnCode.*;
|
import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
|
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collection;
|
import java.util.Collections;
|
import java.util.EnumSet;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.SortedSet;
|
import java.util.TreeSet;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.LocalizableMessageBuilder;
|
import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
|
import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
|
import org.forgerock.opendj.config.AggregationPropertyDefinition;
|
import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
|
import org.forgerock.opendj.config.BooleanPropertyDefinition;
|
import org.forgerock.opendj.config.Configuration;
|
import org.forgerock.opendj.config.ConfigurationClient;
|
import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
|
import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
|
import org.forgerock.opendj.config.DefinitionDecodingException;
|
import org.forgerock.opendj.config.EnumPropertyDefinition;
|
import org.forgerock.opendj.config.InstantiableRelationDefinition;
|
import org.forgerock.opendj.config.ManagedObjectDefinition;
|
import org.forgerock.opendj.config.ManagedObjectNotFoundException;
|
import org.forgerock.opendj.config.ManagedObjectPath;
|
import org.forgerock.opendj.config.PropertyDefinition;
|
import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
|
import org.forgerock.opendj.config.PropertyDefinitionVisitor;
|
import org.forgerock.opendj.config.PropertyException;
|
import org.forgerock.opendj.config.PropertyOption;
|
import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
|
import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
|
import org.forgerock.opendj.config.client.ManagedObject;
|
import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
|
import org.forgerock.opendj.config.client.ManagementContext;
|
import org.forgerock.opendj.ldap.AuthorizationException;
|
import org.forgerock.opendj.ldap.LdapException;
|
import org.forgerock.util.Reject;
|
|
import com.forgerock.opendj.cli.ClientException;
|
import com.forgerock.opendj.cli.ConsoleApplication;
|
import com.forgerock.opendj.cli.HelpCallback;
|
import com.forgerock.opendj.cli.Menu;
|
import com.forgerock.opendj.cli.MenuBuilder;
|
import com.forgerock.opendj.cli.MenuCallback;
|
import com.forgerock.opendj.cli.MenuResult;
|
import com.forgerock.opendj.cli.ReturnCode;
|
import com.forgerock.opendj.cli.TableBuilder;
|
import com.forgerock.opendj.cli.TextTablePrinter;
|
|
import static org.forgerock.opendj.config.dsconfig.DSConfig.*;
|
|
/**
|
* Common methods used for interactively editing properties.
|
*/
|
final class PropertyValueEditor {
|
|
/**
|
* A menu call-back which can be used to dynamically create new components when configuring aggregation based
|
* properties.
|
*/
|
private final class CreateComponentCallback<C extends ConfigurationClient, S extends Configuration> implements
|
MenuCallback<String> {
|
|
/** The aggregation property definition. */
|
private final AggregationPropertyDefinition<C, S> pd;
|
|
/**
|
* Creates a new component create call-back for the provided aggregation property definition.
|
*/
|
private CreateComponentCallback(AggregationPropertyDefinition<C, S> pd) {
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<String> invoke(ConsoleApplication app) throws ClientException {
|
try {
|
// First get the parent managed object.
|
InstantiableRelationDefinition<?, ?> rd = pd.getRelationDefinition();
|
ManagedObjectPath<?, ?> path = pd.getParentPath();
|
LocalizableMessage ufn = rd.getUserFriendlyName();
|
|
ManagedObject<?> parent;
|
try {
|
parent = context.getManagedObject(path);
|
} catch (AuthorizationException e) {
|
LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
|
throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
|
} catch (DefinitionDecodingException e) {
|
LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
|
LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(pufn, pufn, pufn);
|
throw new ClientException(ReturnCode.OTHER, msg);
|
} catch (ManagedObjectDecodingException e) {
|
LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
|
LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(pufn);
|
throw new ClientException(ReturnCode.OTHER, msg, e);
|
} catch (LdapException e) {
|
LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
|
throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
|
} catch (ManagedObjectNotFoundException e) {
|
LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
|
LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(pufn);
|
return interactivePrintOrThrowError(app, msg, NO_SUCH_OBJECT);
|
}
|
|
// Now let the user create the child component.
|
app.println();
|
app.println();
|
return CreateSubCommandHandler.createManagedObject(app, context, parent, rd);
|
} catch (ClientException e) {
|
// FIXME: should really do something better with the exception
|
// handling here. For example, if a authz or communications
|
// exception occurs then the application should exit.
|
app.errPrintln();
|
app.errPrintln(e.getMessageObject());
|
app.errPrintln();
|
app.pressReturnToContinue();
|
return MenuResult.cancel();
|
}
|
}
|
}
|
|
/**
|
* A help call-back which displays a description and summary of a component and its properties.
|
*/
|
private static final class ComponentHelpCallback implements HelpCallback {
|
|
/** The managed object being edited. */
|
private final ManagedObject<?> mo;
|
|
/** The properties that can be edited. */
|
private final Collection<PropertyDefinition<?>> properties;
|
|
/** Creates a new component helper for the specified property. */
|
private ComponentHelpCallback(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c) {
|
this.mo = mo;
|
this.properties = c;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void display(ConsoleApplication app) {
|
app.println();
|
HelpSubCommandHandler.displaySingleComponent(app, mo, properties);
|
app.println();
|
app.pressReturnToContinue();
|
}
|
}
|
|
/**
|
* A simple interface for querying and retrieving common default behavior properties.
|
*/
|
private static final class DefaultBehaviorQuery<T> {
|
|
/**
|
* The type of default behavior.
|
*/
|
private enum Type {
|
/**
|
* Alias default behavior.
|
*/
|
ALIAS,
|
|
/**
|
* Defined default behavior.
|
*/
|
DEFINED,
|
|
/**
|
* Inherited default behavior.
|
*/
|
INHERITED,
|
|
/**
|
* Undefined default behavior.
|
*/
|
UNDEFINED;
|
}
|
|
/**
|
* Create a new default behavior query object based on the provied property definition.
|
*
|
* @param <T>
|
* The type of property definition.
|
* @param pd
|
* The property definition.
|
* @return The default behavior query object.
|
*/
|
public static <T> DefaultBehaviorQuery<T> query(PropertyDefinition<T> pd) {
|
DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>, PropertyDefinition<T>> visitor
|
= new DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>, PropertyDefinition<T>>() {
|
|
/** {@inheritDoc} */
|
@Override
|
public DefaultBehaviorQuery<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d,
|
PropertyDefinition<T> p) {
|
AbstractManagedObjectDefinition<?, ?> mod = d.getManagedObjectDefinition();
|
String propertyName = d.getPropertyName();
|
PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
|
|
DefaultBehaviorQuery<?> query = query(pd2);
|
return new DefaultBehaviorQuery<T>(Type.INHERITED, query.getAliasDescription());
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public DefaultBehaviorQuery<T> visitAlias(AliasDefaultBehaviorProvider<T> d,
|
PropertyDefinition<T> p) {
|
return new DefaultBehaviorQuery<T>(Type.ALIAS, d.getSynopsis());
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public DefaultBehaviorQuery<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
|
PropertyDefinition<T> p) {
|
return new DefaultBehaviorQuery<T>(Type.DEFINED, null);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public DefaultBehaviorQuery<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d,
|
PropertyDefinition<T> p) {
|
AbstractManagedObjectDefinition<?, ?> mod = d.getManagedObjectDefinition();
|
String propertyName = d.getPropertyName();
|
PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
|
|
DefaultBehaviorQuery<?> query = query(pd2);
|
return new DefaultBehaviorQuery<T>(Type.INHERITED, query.getAliasDescription());
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public DefaultBehaviorQuery<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
|
PropertyDefinition<T> p) {
|
return new DefaultBehaviorQuery<T>(Type.UNDEFINED, null);
|
}
|
};
|
|
return pd.getDefaultBehaviorProvider().accept(visitor, pd);
|
}
|
|
/**
|
* The description of the behavior if it is an alias default behavior.
|
*/
|
private final LocalizableMessage aliasDescription;
|
|
/** The type of behavior. */
|
private final Type type;
|
|
/** Private constructor. */
|
private DefaultBehaviorQuery(Type type, LocalizableMessage aliasDescription) {
|
this.type = type;
|
this.aliasDescription = aliasDescription;
|
}
|
|
/**
|
* Gets the detailed description of this default behavior if it is an alias default behavior or if it inherits
|
* from an alias default behavior.
|
*
|
* @return Returns the detailed description of this default behavior if it is an alias default behavior or if it
|
* inherits from an alias default behavior, otherwise <code>null</code>.
|
*/
|
public LocalizableMessage getAliasDescription() {
|
return aliasDescription;
|
}
|
|
/**
|
* Determines whether or not the default behavior is alias.
|
*
|
* @return Returns <code>true</code> if the default behavior is alias.
|
*/
|
public boolean isAlias() {
|
return type == Type.ALIAS;
|
}
|
|
/**
|
* Determines whether or not the default behavior is defined.
|
*
|
* @return Returns <code>true</code> if the default behavior is defined.
|
*/
|
public boolean isDefined() {
|
return type == Type.DEFINED;
|
}
|
|
/**
|
* Determines whether or not the default behavior is inherited.
|
*
|
* @return Returns <code>true</code> if the default behavior is inherited.
|
*/
|
public boolean isInherited() {
|
return type == Type.INHERITED;
|
}
|
|
/**
|
* Determines whether or not the default behavior is undefined.
|
*
|
* @return Returns <code>true</code> if the default behavior is undefined.
|
*/
|
public boolean isUndefined() {
|
return type == Type.UNDEFINED;
|
}
|
|
}
|
|
/**
|
* A property definition visitor which initializes mandatory properties.
|
*/
|
private final class MandatoryPropertyInitializer extends PropertyDefinitionVisitor<MenuResult<Void>, Void>
|
implements MenuCallback<Void> {
|
|
/** Any exception that was caught during processing. */
|
private ClientException e;
|
|
/** The managed object being edited. */
|
private final ManagedObject<?> mo;
|
|
/** The property to be edited. */
|
private final PropertyDefinition<?> pd;
|
|
/** Creates a new property editor for the specified property. */
|
private MandatoryPropertyInitializer(ManagedObject<?> mo, PropertyDefinition<?> pd) {
|
this.mo = mo;
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Void> invoke(ConsoleApplication app) throws ClientException {
|
displayPropertyHeader(app, pd);
|
|
MenuResult<Void> result = pd.accept(this, null);
|
|
if (e != null) {
|
throw e;
|
}
|
return result;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <C extends ConfigurationClient, S extends Configuration> MenuResult<Void> visitAggregation(
|
AggregationPropertyDefinition<C, S> d, Void p) {
|
MenuBuilder<String> builder = new MenuBuilder<String>(app);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
|
if (d.hasOption(PropertyOption.MULTI_VALUED)) {
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENT_MULTI.get(rd.getUserFriendlyPluralName(),
|
d.getName()));
|
builder.setAllowMultiSelect(true);
|
} else {
|
builder.setPrompt(
|
INFO_EDITOR_PROMPT_SELECT_COMPONENT_SINGLE.get(rd.getUserFriendlyName(), d.getName()));
|
}
|
|
// Create a list of possible names.
|
Set<String> values = new TreeSet<String>(d);
|
ManagedObjectPath<?, ?> path = d.getParentPath();
|
try {
|
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
|
} catch (AuthorizationException e) {
|
this.e = new ClientException(
|
ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e.getMessage()));
|
return MenuResult.quit();
|
} catch (ManagedObjectNotFoundException e) {
|
this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
|
return MenuResult.cancel();
|
} catch (LdapException e) {
|
this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
|
return MenuResult.quit();
|
}
|
|
for (String value : values) {
|
LocalizableMessage option = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(option, MenuResult.success(value));
|
}
|
MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
|
builder.addNumberedOption(
|
INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(rd.getUserFriendlyName()), callback);
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
if (app.isMenuDrivenMode()) {
|
builder.addCancelOption(true);
|
}
|
builder.addQuitOption();
|
|
Menu<String> menu = builder.toMenu();
|
try {
|
app.println();
|
MenuResult<String> result = menu.run();
|
|
if (result.isQuit()) {
|
return MenuResult.quit();
|
} else if (result.isCancel()) {
|
return MenuResult.cancel();
|
} else {
|
Collection<String> newValues = result.getValues();
|
SortedSet<String> oldValues = new TreeSet<String>(mo.getPropertyValues(d));
|
mo.setPropertyValues(d, newValues);
|
isLastChoiceReset = false;
|
registerModification(d, new TreeSet<String>(newValues), oldValues);
|
return MenuResult.success();
|
}
|
} catch (ClientException e) {
|
this.e = e;
|
return MenuResult.cancel();
|
}
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Void> visitBoolean(BooleanPropertyDefinition d, Void p) {
|
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE.get(d.getName()));
|
|
builder.addNumberedOption(INFO_VALUE_TRUE.get(), MenuResult.success(true));
|
builder.addNumberedOption(INFO_VALUE_FALSE.get(), MenuResult.success(false));
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
if (app.isMenuDrivenMode()) {
|
builder.addCancelOption(true);
|
}
|
builder.addQuitOption();
|
|
Menu<Boolean> menu = builder.toMenu();
|
try {
|
app.println();
|
MenuResult<Boolean> result = menu.run();
|
|
if (result.isQuit()) {
|
return MenuResult.quit();
|
} else if (result.isCancel()) {
|
return MenuResult.cancel();
|
} else {
|
Collection<Boolean> newValues = result.getValues();
|
SortedSet<Boolean> oldValues = new TreeSet<Boolean>(mo.getPropertyValues(d));
|
mo.setPropertyValues(d, newValues);
|
isLastChoiceReset = false;
|
registerModification(d, new TreeSet<Boolean>(newValues), oldValues);
|
return MenuResult.success();
|
}
|
} catch (ClientException e) {
|
this.e = e;
|
return MenuResult.cancel();
|
}
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <E extends Enum<E>> MenuResult<Void> visitEnum(EnumPropertyDefinition<E> d, Void x) {
|
MenuBuilder<E> builder = new MenuBuilder<E>(app);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
if (d.hasOption(PropertyOption.MULTI_VALUED)) {
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_MULTI.get(d.getName()));
|
builder.setAllowMultiSelect(true);
|
} else {
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE.get(d.getName()));
|
}
|
|
Set<E> values = new TreeSet<E>(d);
|
values.addAll(EnumSet.allOf(d.getEnumClass()));
|
for (E value : values) {
|
LocalizableMessage option = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(option, MenuResult.success(value));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
if (app.isMenuDrivenMode()) {
|
builder.addCancelOption(true);
|
}
|
builder.addQuitOption();
|
|
Menu<E> menu = builder.toMenu();
|
try {
|
app.println();
|
MenuResult<E> result = menu.run();
|
|
if (result.isQuit()) {
|
return MenuResult.quit();
|
} else if (result.isCancel()) {
|
return MenuResult.cancel();
|
} else {
|
Collection<E> newValues = result.getValues();
|
SortedSet<E> oldValues = new TreeSet<E>(mo.getPropertyValues(d));
|
mo.setPropertyValues(d, newValues);
|
isLastChoiceReset = false;
|
registerModification(d, new TreeSet<E>(newValues), oldValues);
|
return MenuResult.success();
|
}
|
} catch (ClientException e) {
|
this.e = e;
|
return MenuResult.cancel();
|
}
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <T> MenuResult<Void> visitUnknown(PropertyDefinition<T> d, Void p) {
|
app.println();
|
displayPropertySyntax(app, d);
|
|
// Set the new property value(s).
|
try {
|
SortedSet<T> values = readPropertyValues(app, mo.getManagedObjectDefinition(), d);
|
SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
|
mo.setPropertyValues(d, values);
|
isLastChoiceReset = false;
|
registerModification(d, values, oldValues);
|
return MenuResult.success();
|
} catch (ClientException e) {
|
this.e = e;
|
return MenuResult.cancel();
|
}
|
}
|
|
}
|
|
/**
|
* A menu call-back for editing a modifiable multi-valued property.
|
*/
|
private final class MultiValuedPropertyEditor extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
|
implements MenuCallback<Boolean> {
|
|
/** Any exception that was caught during processing. */
|
private ClientException e;
|
|
/** The managed object being edited. */
|
private final ManagedObject<?> mo;
|
|
/** The property to be edited. */
|
private final PropertyDefinition<?> pd;
|
|
/** Creates a new property editor for the specified property. */
|
private MultiValuedPropertyEditor(ManagedObject<?> mo, PropertyDefinition<?> pd) {
|
Reject.ifFalse(pd.hasOption(PropertyOption.MULTI_VALUED));
|
|
this.mo = mo;
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
displayPropertyHeader(app, pd);
|
|
MenuResult<Boolean> result = pd.accept(this, null);
|
if (e != null) {
|
throw e;
|
}
|
return result;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <C extends ConfigurationClient, S extends Configuration> MenuResult<Boolean> visitAggregation(
|
final AggregationPropertyDefinition<C, S> d, Void p) {
|
final SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
|
final SortedSet<String> oldValues = mo.getPropertyValues(d);
|
final SortedSet<String> currentValues = mo.getPropertyValues(d);
|
final InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
|
final LocalizableMessage ufpn = rd.getUserFriendlyPluralName();
|
|
boolean isFirst = true;
|
while (true) {
|
if (!isFirst) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
|
} else {
|
isFirst = false;
|
}
|
|
if (currentValues.size() > 1) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_COMPONENT_SUMMARY.get(d.getName(), ufpn));
|
app.println();
|
displayPropertyValues(app, d, currentValues);
|
}
|
|
// Create a list of possible names.
|
final Set<String> values = new TreeSet<String>(d);
|
ManagedObjectPath<?, ?> path = d.getParentPath();
|
try {
|
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
|
} catch (AuthorizationException e) {
|
this.e = new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e
|
.getMessage()));
|
return MenuResult.quit();
|
} catch (ManagedObjectNotFoundException e) {
|
this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
|
return MenuResult.cancel();
|
} catch (LdapException e) {
|
this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
|
return MenuResult.quit();
|
}
|
|
// Create the add values call-back.
|
MenuCallback<Boolean> addCallback = null;
|
values.removeAll(currentValues);
|
if (!values.isEmpty()) {
|
addCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuBuilder<String> builder = new MenuBuilder<String>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_ADD.get(ufpn));
|
builder.setAllowMultiSelect(true);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
for (String value : values) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(svalue, MenuResult.success(value));
|
}
|
MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
|
builder.addNumberedOption(
|
INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(rd.getUserFriendlyName()), callback);
|
|
if (values.size() > 1) {
|
// No point in having this option if there's only one
|
// possible value.
|
builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_COMPONENTS.get(ufpn),
|
MenuResult.success(values));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
|
builder.addCancelOption(true);
|
builder.addQuitOption();
|
|
app.println();
|
app.println();
|
Menu<String> menu = builder.toMenu();
|
MenuResult<String> result = menu.run();
|
|
if (result.isSuccess()) {
|
// Set the new property value(s).
|
Collection<String> addedValues = result.getValues();
|
currentValues.addAll(addedValues);
|
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
|
};
|
}
|
|
// Create the remove values call-back.
|
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuBuilder<String> builder = new MenuBuilder<String>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_REMOVE.get(ufpn));
|
builder.setAllowMultiSelect(true);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
for (String value : currentValues) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(svalue, MenuResult.success(value));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
|
builder.addCancelOption(true);
|
builder.addQuitOption();
|
|
app.println();
|
app.println();
|
Menu<String> menu = builder.toMenu();
|
MenuResult<String> result = menu.run();
|
|
if (result.isSuccess()) {
|
// Set the new property value(s).
|
Collection<String> removedValues = result.getValues();
|
currentValues.removeAll(removedValues);
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
|
};
|
|
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
|
removeCallback);
|
if (!result.isAgain()) {
|
return result;
|
}
|
}
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <T extends Enum<T>> MenuResult<Boolean> visitEnum(final EnumPropertyDefinition<T> d, Void p) {
|
final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
|
final SortedSet<T> oldValues = mo.getPropertyValues(d);
|
final SortedSet<T> currentValues = mo.getPropertyValues(d);
|
|
boolean isFirst = true;
|
while (true) {
|
if (!isFirst) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
|
} else {
|
isFirst = false;
|
}
|
|
if (currentValues.size() > 1) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
|
app.println();
|
displayPropertyValues(app, d, currentValues);
|
}
|
|
// Create the add values call-back.
|
MenuCallback<Boolean> addCallback = null;
|
|
final EnumSet<T> values = EnumSet.allOf(d.getEnumClass());
|
values.removeAll(currentValues);
|
|
if (!values.isEmpty()) {
|
addCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuBuilder<T> builder = new MenuBuilder<T>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_ADD.get());
|
builder.setAllowMultiSelect(true);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
for (T value : values) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(svalue, MenuResult.success(value));
|
}
|
|
if (values.size() > 1) {
|
// No point in having this option if there's only one
|
// possible value.
|
builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_VALUES.get(),
|
MenuResult.success(values));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
|
builder.addCancelOption(true);
|
builder.addQuitOption();
|
|
app.println();
|
app.println();
|
Menu<T> menu = builder.toMenu();
|
MenuResult<T> result = menu.run();
|
|
if (result.isSuccess()) {
|
|
// Set the new property value(s).
|
Collection<T> addedValues = result.getValues();
|
currentValues.addAll(addedValues);
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
|
};
|
}
|
|
// Create the remove values call-back.
|
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuBuilder<T> builder = new MenuBuilder<T>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
|
builder.setAllowMultiSelect(true);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
for (T value : currentValues) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(svalue, MenuResult.success(value));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
|
builder.addCancelOption(true);
|
builder.addQuitOption();
|
|
app.println();
|
app.println();
|
Menu<T> menu = builder.toMenu();
|
MenuResult<T> result = menu.run();
|
|
if (result.isSuccess()) {
|
// Set the new property value(s).
|
Collection<T> removedValues = result.getValues();
|
currentValues.removeAll(removedValues);
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
|
};
|
|
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
|
removeCallback);
|
if (!result.isAgain()) {
|
return result;
|
}
|
}
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d, Void p) {
|
app.println();
|
displayPropertySyntax(app, d);
|
|
final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
|
final SortedSet<T> oldValues = mo.getPropertyValues(d);
|
final SortedSet<T> currentValues = mo.getPropertyValues(d);
|
|
boolean isFirst = true;
|
while (true) {
|
if (!isFirst) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
|
} else {
|
isFirst = false;
|
}
|
|
if (currentValues.size() > 1) {
|
app.println();
|
app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
|
app.println();
|
displayPropertyValues(app, d, currentValues);
|
}
|
|
// Create the add values call-back.
|
MenuCallback<Boolean> addCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
app.println();
|
SortedSet<T> previousValues = new TreeSet<T>(currentValues);
|
readPropertyValues(app, mo.getManagedObjectDefinition(), d, currentValues);
|
SortedSet<T> addedValues = new TreeSet<T>(currentValues);
|
addedValues.removeAll(previousValues);
|
isLastChoiceReset = false;
|
return MenuResult.success(false);
|
}
|
|
};
|
|
// Create the remove values call-back.
|
MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuBuilder<T> builder = new MenuBuilder<T>(app);
|
|
builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
|
builder.setAllowMultiSelect(true);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
|
for (T value : currentValues) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
builder.addNumberedOption(svalue, MenuResult.success(value));
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
|
builder.addCancelOption(true);
|
builder.addQuitOption();
|
|
app.println();
|
app.println();
|
Menu<T> menu = builder.toMenu();
|
MenuResult<T> result = menu.run();
|
|
if (result.isSuccess()) {
|
// Set the new property value(s).
|
Collection<T> removedValues = result.getValues();
|
currentValues.removeAll(removedValues);
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
|
};
|
|
MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
|
removeCallback);
|
if (!result.isAgain()) {
|
return result;
|
}
|
}
|
}
|
|
/**
|
* Generate an appropriate menu option for a property which asks the user whether or not they want to keep the
|
* property's current settings.
|
*/
|
private <T> LocalizableMessage getKeepDefaultValuesMenuOption(PropertyDefinition<T> pd,
|
SortedSet<T> defaultValues, SortedSet<T> oldValues, SortedSet<T> currentValues) {
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
|
boolean isModified = !currentValues.equals(oldValues);
|
boolean isDefault = currentValues.equals(defaultValues);
|
|
if (isModified) {
|
switch (currentValues.size()) {
|
case 0:
|
if (query.isAlias()) {
|
return INFO_EDITOR_OPTION_USE_DEFAULT_ALIAS.get(query.getAliasDescription());
|
} else if (query.isInherited()) {
|
if (query.getAliasDescription() != null) {
|
return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
|
} else {
|
return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
|
}
|
} else {
|
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
|
}
|
case 1:
|
LocalizableMessage svalue = getPropertyValues(pd, currentValues);
|
if (isDefault) {
|
if (query.isInherited()) {
|
return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUE.get(svalue);
|
} else {
|
return INFO_EDITOR_OPTION_USE_DEFAULT_VALUE.get(svalue);
|
}
|
} else {
|
return INFO_EDITOR_OPTION_USE_VALUE.get(svalue);
|
}
|
default:
|
if (isDefault) {
|
if (query.isInherited()) {
|
return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUES.get();
|
} else {
|
return INFO_EDITOR_OPTION_USE_DEFAULT_VALUES.get();
|
}
|
} else {
|
return INFO_EDITOR_OPTION_USE_VALUES.get();
|
}
|
}
|
} else {
|
switch (currentValues.size()) {
|
case 0:
|
if (query.isAlias()) {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query.getAliasDescription());
|
} else if (query.isInherited()) {
|
if (query.getAliasDescription() != null) {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
|
}
|
} else {
|
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
|
}
|
case 1:
|
LocalizableMessage svalue = getPropertyValues(pd, currentValues);
|
if (isDefault) {
|
if (query.isInherited()) {
|
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE.get(svalue);
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
|
}
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
|
}
|
default:
|
if (isDefault) {
|
if (query.isInherited()) {
|
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUES.get();
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUES.get();
|
}
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_VALUES.get();
|
}
|
}
|
}
|
}
|
|
/**
|
* Generate an appropriate menu option which should be used in the case where a property can be reset to its
|
* default behavior.
|
*/
|
private <T> LocalizableMessage getResetToDefaultValuesMenuOption(PropertyDefinition<T> pd,
|
SortedSet<T> defaultValues, SortedSet<T> currentValues) {
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
|
|
if (!isMandatory && query.isAlias()) {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query.getAliasDescription());
|
} else if (query.isDefined()) {
|
// Only show this option if the current value is different
|
// to the default.
|
if (!currentValues.equals(defaultValues)) {
|
LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
|
if (defaultValues.size() > 1) {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUES.get(svalue);
|
} else {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
|
}
|
} else {
|
return null;
|
}
|
} else if (!isMandatory && query.isInherited()) {
|
if (defaultValues.isEmpty()) {
|
if (query.getAliasDescription() != null) {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
|
} else {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
|
}
|
} else {
|
LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
|
if (defaultValues.size() > 1) {
|
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUES.get(svalue);
|
} else {
|
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
|
}
|
}
|
} else if (!isMandatory && query.isUndefined()) {
|
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
|
} else {
|
return null;
|
}
|
}
|
|
/** Common menu processing. */
|
private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d, ConsoleApplication app,
|
final SortedSet<T> defaultValues, final SortedSet<T> oldValues, final SortedSet<T> currentValues,
|
MenuCallback<Boolean> addCallback, MenuCallback<Boolean> removeCallback) {
|
// Construct a menu of actions.
|
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
|
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
|
|
// First option is for leaving the property unchanged or
|
// applying changes, but only if the state of the property is valid.
|
if (!d.hasOption(PropertyOption.MANDATORY) || !currentValues.isEmpty()) {
|
MenuResult<Boolean> result;
|
if (!oldValues.equals(currentValues)) {
|
result = MenuResult.success(true);
|
} else {
|
result = MenuResult.cancel();
|
}
|
|
LocalizableMessage option = getKeepDefaultValuesMenuOption(d, defaultValues, oldValues, currentValues);
|
builder.addNumberedOption(option, result);
|
builder.setDefault(LocalizableMessage.raw("1"), result);
|
}
|
|
// Add an option for adding some values.
|
if (addCallback != null) {
|
int i = builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ONE_OR_MORE_VALUES.get(), addCallback);
|
if (d.hasOption(PropertyOption.MANDATORY) && currentValues.isEmpty()) {
|
builder.setDefault(LocalizableMessage.raw("%d", i), addCallback);
|
}
|
}
|
|
// Add options for removing values if applicable.
|
if (!currentValues.isEmpty()) {
|
builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ONE_OR_MORE_VALUES.get(), removeCallback);
|
}
|
|
// Add options for removing all values and for resetting the
|
// property to its default behavior.
|
LocalizableMessage resetOption = null;
|
if (!currentValues.equals(defaultValues)) {
|
resetOption = getResetToDefaultValuesMenuOption(d, defaultValues, currentValues);
|
}
|
|
if (!currentValues.isEmpty() && (resetOption == null || !defaultValues.isEmpty())) {
|
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
isLastChoiceReset = false;
|
currentValues.clear();
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
}
|
};
|
builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ALL_VALUES.get(), callback);
|
}
|
|
if (resetOption != null) {
|
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
currentValues.clear();
|
currentValues.addAll(defaultValues);
|
isLastChoiceReset = true;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
}
|
|
};
|
|
builder.addNumberedOption(resetOption, callback);
|
}
|
|
// Add an option for undoing any changes.
|
if (!oldValues.equals(currentValues)) {
|
MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
|
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
currentValues.clear();
|
currentValues.addAll(oldValues);
|
isLastChoiceReset = false;
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
}
|
|
};
|
|
builder.addNumberedOption(INFO_EDITOR_OPTION_REVERT_CHANGES.get(), callback);
|
}
|
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
builder.addQuitOption();
|
|
Menu<Boolean> menu = builder.toMenu();
|
MenuResult<Boolean> result;
|
try {
|
app.println();
|
result = menu.run();
|
} catch (ClientException e) {
|
this.e = e;
|
return null;
|
}
|
|
if (result.isSuccess()) {
|
if (result.getValue()) {
|
|
// Set the new property value(s).
|
mo.setPropertyValues(d, currentValues);
|
|
registerModification(d, currentValues, oldValues);
|
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
// Continue until cancel/apply changes.
|
app.println();
|
return MenuResult.again();
|
}
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
}
|
|
/**
|
* A help call-back which displays a description and summary of a single property.
|
*/
|
private static final class PropertyHelpCallback implements HelpCallback {
|
|
/** The managed object definition. */
|
private final ManagedObjectDefinition<?, ?> d;
|
|
/** The property to be edited. */
|
private final PropertyDefinition<?> pd;
|
|
/** Creates a new property helper for the specified property. */
|
private PropertyHelpCallback(ManagedObjectDefinition<?, ?> d, PropertyDefinition<?> pd) {
|
this.d = d;
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public void display(ConsoleApplication app) {
|
app.println();
|
HelpSubCommandHandler.displayVerboseSingleProperty(app, d, pd.getName());
|
app.println();
|
app.pressReturnToContinue();
|
}
|
}
|
|
/**
|
* A menu call-back for viewing a read-only properties.
|
*/
|
private final class ReadOnlyPropertyViewer extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void> implements
|
MenuCallback<Boolean> {
|
|
/** Any exception that was caught during processing. */
|
private ClientException e;
|
|
/** The managed object being edited. */
|
private final ManagedObject<?> mo;
|
|
/** The property to be edited. */
|
private final PropertyDefinition<?> pd;
|
|
/** Creates a new property editor for the specified property. */
|
private ReadOnlyPropertyViewer(ManagedObject<?> mo, PropertyDefinition<?> pd) {
|
this.mo = mo;
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
MenuResult<Boolean> result = pd.accept(this, null);
|
if (e != null) {
|
throw e;
|
}
|
return result;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <T> MenuResult<Boolean> visitUnknown(PropertyDefinition<T> pd, Void p) {
|
SortedSet<T> values = mo.getPropertyValues(pd);
|
|
app.println();
|
app.println();
|
switch (values.size()) {
|
case 0:
|
// Only alias, undefined, or inherited alias or undefined
|
// properties should apply here.
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
LocalizableMessage aliasDescription = query.getAliasDescription();
|
if (aliasDescription != null) {
|
app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS.get(pd.getName(), aliasDescription));
|
} else {
|
app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS_UNDEFINED.get(pd.getName()));
|
}
|
break;
|
case 1:
|
LocalizableMessage svalue = getPropertyValues(pd, mo);
|
app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUE.get(pd.getName(), svalue));
|
break;
|
default:
|
app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUES.get(pd.getName()));
|
app.println();
|
displayPropertyValues(app, pd, values);
|
break;
|
}
|
|
app.println();
|
boolean result;
|
try {
|
result = app.confirmAction(INFO_EDITOR_PROMPT_READ_ONLY.get(), false);
|
} catch (ClientException e) {
|
this.e = e;
|
return null;
|
}
|
|
if (result) {
|
app.println();
|
HelpSubCommandHandler.displayVerboseSingleProperty(app, mo.getManagedObjectDefinition(), pd.getName());
|
app.println();
|
app.pressReturnToContinue();
|
}
|
|
return MenuResult.again();
|
}
|
}
|
|
/**
|
* A menu call-back for editing a modifiable single-valued property.
|
*/
|
private final class SingleValuedPropertyEditor extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
|
implements MenuCallback<Boolean> {
|
|
/** Any exception that was caught during processing. */
|
private ClientException e;
|
|
/** The managed object being edited. */
|
private final ManagedObject<?> mo;
|
|
/** The property to be edited. */
|
private final PropertyDefinition<?> pd;
|
|
/** Creates a new property editor for the specified property. */
|
private SingleValuedPropertyEditor(ManagedObject<?> mo, PropertyDefinition<?> pd) {
|
Reject.ifFalse(!pd.hasOption(PropertyOption.MULTI_VALUED));
|
|
this.mo = mo;
|
this.pd = pd;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
|
displayPropertyHeader(app, pd);
|
|
MenuResult<Boolean> result = pd.accept(this, null);
|
if (e != null) {
|
throw e;
|
}
|
return result;
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <C extends ConfigurationClient, S extends Configuration> MenuResult<Boolean> visitAggregation(
|
AggregationPropertyDefinition<C, S> d, Void p) {
|
// Construct a menu of actions.
|
MenuBuilder<String> builder = new MenuBuilder<String>(app);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
|
|
DefaultBehaviorQuery<String> query = DefaultBehaviorQuery.query(d);
|
SortedSet<String> currentValues = mo.getPropertyValues(d);
|
SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
|
String currentValue = currentValues.isEmpty() ? null : currentValues.first();
|
String defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
|
|
// First option is for leaving the property unchanged.
|
LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
|
builder.addNumberedOption(option, MenuResult.<String> cancel());
|
builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<String> cancel());
|
|
// Create a list of possible names.
|
final Set<String> values = new TreeSet<String>(d);
|
ManagedObjectPath<?, ?> path = d.getParentPath();
|
InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
|
try {
|
values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
|
} catch (AuthorizationException e) {
|
this.e = new ClientException(
|
ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e.getMessage()));
|
return MenuResult.quit();
|
} catch (ManagedObjectNotFoundException e) {
|
this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
|
return MenuResult.cancel();
|
} catch (LdapException e) {
|
this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
|
return MenuResult.quit();
|
}
|
|
final LocalizableMessage ufn = rd.getUserFriendlyName();
|
for (String value : values) {
|
if (currentValue != null && d.compare(value, currentValue) == 0) {
|
// This option is unnecessary.
|
continue;
|
}
|
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
if (value.equals(defaultValue) && query.isDefined()) {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_COMPONENT.get(ufn, svalue);
|
} else {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_COMPONENT.get(ufn, svalue);
|
}
|
|
builder.addNumberedOption(option, MenuResult.success(value));
|
}
|
MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
|
builder.addNumberedOption(INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(ufn), callback);
|
|
// Third option is to reset the value back to its default.
|
if (mo.isPropertyPresent(d) && !query.isDefined()) {
|
option = getResetToDefaultValuesMenuOption(d);
|
if (option != null) {
|
builder.addNumberedOption(option, MenuResult.<String> success());
|
}
|
}
|
|
return runMenu(d, builder);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public MenuResult<Boolean> visitBoolean(BooleanPropertyDefinition d, Void p) {
|
// Construct a menu of actions.
|
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
|
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
|
|
DefaultBehaviorQuery<Boolean> query = DefaultBehaviorQuery.query(d);
|
SortedSet<Boolean> currentValues = mo.getPropertyValues(d);
|
SortedSet<Boolean> defaultValues = mo.getPropertyDefaultValues(d);
|
|
Boolean currentValue = currentValues.isEmpty() ? null : currentValues.first();
|
Boolean defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
|
|
// First option is for leaving the property unchanged.
|
LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
|
builder.addNumberedOption(option, MenuResult.<Boolean> cancel());
|
builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<Boolean> cancel());
|
|
// The second (and possibly third) option is to always change
|
// the property's value.
|
if (!Boolean.TRUE.equals(currentValue)) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(true));
|
|
if (Boolean.TRUE.equals(defaultValue)) {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
|
} else {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
|
}
|
|
builder.addNumberedOption(option, MenuResult.success(true));
|
}
|
|
if (!Boolean.FALSE.equals(currentValue)) {
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(false));
|
|
if (Boolean.FALSE.equals(defaultValue)) {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
|
} else {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
|
}
|
|
builder.addNumberedOption(option, MenuResult.success(false));
|
}
|
|
// Final option is to reset the value back to its default.
|
if (mo.isPropertyPresent(d) && !query.isDefined()) {
|
option = getResetToDefaultValuesMenuOption(d);
|
if (option != null) {
|
builder.addNumberedOption(option, MenuResult.<Boolean> success());
|
}
|
}
|
|
return runMenu(d, builder);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <E extends Enum<E>> MenuResult<Boolean> visitEnum(EnumPropertyDefinition<E> d, Void p) {
|
// Construct a menu of actions.
|
MenuBuilder<E> builder = new MenuBuilder<E>(app);
|
builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
|
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
|
|
DefaultBehaviorQuery<E> query = DefaultBehaviorQuery.query(d);
|
SortedSet<E> currentValues = mo.getPropertyValues(d);
|
SortedSet<E> defaultValues = mo.getPropertyDefaultValues(d);
|
E currentValue = currentValues.isEmpty() ? null : currentValues.first();
|
E defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
|
|
// First option is for leaving the property unchanged.
|
LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
|
builder.addNumberedOption(option, MenuResult.<E> cancel());
|
builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<E> cancel());
|
|
// Create options for changing to other values.
|
Set<E> values = new TreeSet<E>(d);
|
values.addAll(EnumSet.allOf(d.getEnumClass()));
|
for (E value : values) {
|
if (value.equals(currentValue) && query.isDefined()) {
|
// This option is unnecessary.
|
continue;
|
}
|
|
LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
|
|
if (value.equals(defaultValue) && query.isDefined()) {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
|
} else {
|
option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
|
}
|
|
builder.addNumberedOption(option, MenuResult.success(value));
|
}
|
|
// Third option is to reset the value back to its default.
|
if (mo.isPropertyPresent(d) && !query.isDefined()) {
|
option = getResetToDefaultValuesMenuOption(d);
|
if (option != null) {
|
builder.addNumberedOption(option, MenuResult.<E> success());
|
}
|
}
|
|
return runMenu(d, builder);
|
}
|
|
/** {@inheritDoc} */
|
@Override
|
public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d, Void p) {
|
app.println();
|
displayPropertySyntax(app, d);
|
|
// Construct a menu of actions.
|
MenuBuilder<T> builder = new MenuBuilder<T>(app);
|
builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
|
|
// First option is for leaving the property unchanged.
|
LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
|
builder.addNumberedOption(option, MenuResult.<T> cancel());
|
builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<T> cancel());
|
|
// The second option is to always change the property's value.
|
builder.addNumberedOption(INFO_EDITOR_OPTION_CHANGE_VALUE.get(), new MenuCallback<T>() {
|
|
@Override
|
public MenuResult<T> invoke(ConsoleApplication app) throws ClientException {
|
app.println();
|
Set<T> values = readPropertyValues(app, mo.getManagedObjectDefinition(), d);
|
return MenuResult.success(values);
|
}
|
|
});
|
|
// Third option is to reset the value back to its default.
|
if (mo.isPropertyPresent(d)) {
|
option = getResetToDefaultValuesMenuOption(d);
|
if (option != null) {
|
builder.addNumberedOption(option, MenuResult.<T> success());
|
}
|
}
|
|
return runMenu(d, builder);
|
}
|
|
/**
|
* Generate an appropriate menu option for a property which asks the user whether or not they want to keep the
|
* property's current settings.
|
*/
|
private <T> LocalizableMessage getKeepDefaultValuesMenuOption(PropertyDefinition<T> pd) {
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
SortedSet<T> currentValues = mo.getPropertyValues(pd);
|
SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
|
|
if (query.isDefined() && currentValues.equals(defaultValues)) {
|
LocalizableMessage svalue = getPropertyValues(pd, currentValues);
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
|
} else if (mo.isPropertyPresent(pd)) {
|
LocalizableMessage svalue = getPropertyValues(pd, currentValues);
|
return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
|
} else if (query.isAlias()) {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query.getAliasDescription());
|
} else if (query.isInherited()) {
|
if (defaultValues.isEmpty()) {
|
if (query.getAliasDescription() != null) {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
|
} else {
|
return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
|
}
|
} else {
|
LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
|
return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE.get(svalue);
|
}
|
} else {
|
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
|
}
|
}
|
|
/**
|
* Generate an appropriate menu option which should be used in the case where a property can be reset to its
|
* default behavior.
|
*/
|
private <T> LocalizableMessage getResetToDefaultValuesMenuOption(PropertyDefinition<T> pd) {
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
SortedSet<T> currentValues = mo.getPropertyValues(pd);
|
SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
|
|
boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
|
|
if (!isMandatory && query.isAlias()) {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query.getAliasDescription());
|
} else if (query.isDefined()) {
|
// Only show this option if the current value is different
|
// to the default.
|
if (!currentValues.equals(defaultValues)) {
|
LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
|
}
|
return null;
|
} else if (!isMandatory && query.isInherited()) {
|
if (defaultValues.isEmpty()) {
|
if (query.getAliasDescription() != null) {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
|
} else {
|
return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
|
}
|
} else {
|
LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
|
return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
|
}
|
} else if (!isMandatory && query.isUndefined()) {
|
return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
|
}
|
return null;
|
}
|
|
/** Common menu processing. */
|
private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d, MenuBuilder<T> builder) {
|
builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
|
builder.addQuitOption();
|
|
Menu<T> menu = builder.toMenu();
|
MenuResult<T> result;
|
try {
|
app.println();
|
result = menu.run();
|
} catch (ClientException e) {
|
this.e = e;
|
return null;
|
}
|
|
if (result.isSuccess()) {
|
// Set the new property value(s).
|
Collection<T> newValues = result.getValues();
|
SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
|
mo.setPropertyValues(d, newValues);
|
|
// If there are no newValues when we do a reset.
|
isLastChoiceReset = newValues.isEmpty();
|
registerModification(d, new TreeSet<T>(newValues), oldValues);
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else if (result.isCancel()) {
|
app.println();
|
app.pressReturnToContinue();
|
return MenuResult.success(false);
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
}
|
|
/** Display a title and a description of the property. */
|
private static void displayPropertyHeader(ConsoleApplication app, PropertyDefinition<?> pd) {
|
app.println();
|
app.println();
|
app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY.get(pd.getName()));
|
app.println();
|
app.println(pd.getSynopsis(), 4);
|
if (pd.getDescription() != null) {
|
app.println();
|
app.println(pd.getDescription(), 4);
|
}
|
}
|
|
/** Display a property's syntax. */
|
private static <T> void displayPropertySyntax(ConsoleApplication app, PropertyDefinition<T> d) {
|
PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
|
|
TableBuilder builder = new TableBuilder();
|
builder.startRow();
|
builder.appendCell(INFO_EDITOR_HEADING_SYNTAX.get());
|
builder.appendCell(b.getUsage(d));
|
|
TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
|
printer.setDisplayHeadings(false);
|
printer.setIndentWidth(4);
|
printer.setColumnWidth(1, 0);
|
builder.print(printer);
|
}
|
|
/** Display a table of property values. */
|
private static <T> void displayPropertyValues(ConsoleApplication app, PropertyDefinition<T> pd,
|
Collection<T> values) {
|
TableBuilder builder = new TableBuilder();
|
PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null, null, false);
|
|
int sz = values.size();
|
boolean useMultipleColumns = (sz >= MULTI_COLUMN_THRESHOLD);
|
int rows = sz;
|
if (useMultipleColumns) {
|
// Display in two columns the first column should contain
|
// half the values. If there are an odd number of columns
|
// then the first column should contain an additional value
|
// (e.g. if there are 23 values, the first column should
|
// contain 12 values and the second column 11 values).
|
rows /= 2;
|
rows += sz % 2;
|
}
|
|
List<T> vl = new ArrayList<T>(values);
|
for (int i = 0, j = rows; i < rows; i++, j++) {
|
builder.startRow();
|
builder.appendCell("*)");
|
builder.appendCell(valuePrinter.print(pd, vl.get(i)));
|
|
if (useMultipleColumns && j < sz) {
|
builder.appendCell();
|
builder.appendCell("*)");
|
builder.appendCell(valuePrinter.print(pd, vl.get(j)));
|
}
|
}
|
|
TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
|
printer.setDisplayHeadings(false);
|
printer.setIndentWidth(4);
|
printer.setColumnWidth(1, 0);
|
if (useMultipleColumns) {
|
printer.setColumnWidth(2, 2);
|
printer.setColumnWidth(4, 0);
|
}
|
builder.print(printer);
|
}
|
|
/** Display the set of values associated with a property. */
|
private static <T> LocalizableMessage getPropertyValues(PropertyDefinition<T> pd, Collection<T> values) {
|
if (values.isEmpty()) {
|
// There are no values or default values. Display the default
|
// behavior for alias values.
|
DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
|
LocalizableMessage content = query.getAliasDescription();
|
if (content != null) {
|
return content;
|
}
|
return LocalizableMessage.raw("-");
|
} else {
|
PropertyValuePrinter printer = new PropertyValuePrinter(null, null, false);
|
LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
|
|
boolean isFirst = true;
|
for (T value : values) {
|
if (!isFirst) {
|
builder.append(", ");
|
}
|
builder.append(printer.print(pd, value));
|
isFirst = false;
|
}
|
|
return builder.toMessage();
|
}
|
}
|
|
/** Display the set of values associated with a property. */
|
private static <T> LocalizableMessage getPropertyValues(PropertyDefinition<T> pd, ManagedObject<?> mo) {
|
SortedSet<T> values = mo.getPropertyValues(pd);
|
return getPropertyValues(pd, values);
|
}
|
|
/** Read new values for a property. */
|
private static <T> SortedSet<T> readPropertyValues(ConsoleApplication app, ManagedObjectDefinition<?, ?> d,
|
PropertyDefinition<T> pd) throws ClientException {
|
SortedSet<T> values = new TreeSet<T>(pd);
|
readPropertyValues(app, d, pd, values);
|
return values;
|
}
|
|
/** Add values to a property. */
|
private static <T> void readPropertyValues(ConsoleApplication app, ManagedObjectDefinition<?, ?> d,
|
PropertyDefinition<T> pd, SortedSet<T> values) throws ClientException {
|
// Make sure there is at least one value if mandatory and empty.
|
if (values.isEmpty()) {
|
while (true) {
|
try {
|
LocalizableMessage prompt;
|
|
if (pd.hasOption(PropertyOption.MANDATORY)) {
|
prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE.get(pd.getName());
|
} else {
|
prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE_OPTIONAL.get(pd.getName());
|
}
|
|
app.println();
|
String s = app.readLineOfInput(prompt);
|
if (s.trim().length() == 0 && !pd.hasOption(PropertyOption.MANDATORY)) {
|
return;
|
}
|
|
T value = pd.decodeValue(s);
|
if (values.contains(value)) {
|
// Prevent addition of duplicates.
|
app.println();
|
app.println(ERR_EDITOR_READ_FIRST_DUPLICATE.get(s));
|
} else {
|
values.add(value);
|
}
|
|
break;
|
} catch (PropertyException e) {
|
app.errPrintln();
|
app.errPrintln(ArgumentExceptionFactory.adaptPropertyException(e, d).getMessageObject());
|
}
|
}
|
}
|
|
if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
|
// Prompt for more values if multi-valued.
|
while (true) {
|
try {
|
LocalizableMessage prompt = INFO_EDITOR_PROMPT_READ_NEXT_VALUE.get(pd.getName());
|
|
app.println();
|
String s = app.readLineOfInput(prompt);
|
if (s.trim().length() == 0) {
|
return;
|
}
|
|
T value = pd.decodeValue(s);
|
if (values.contains(value)) {
|
// Prevent addition of duplicates.
|
app.println();
|
app.println(ERR_EDITOR_READ_NEXT_DUPLICATE.get(s));
|
} else {
|
values.add(value);
|
}
|
} catch (PropertyException e) {
|
app.errPrintln();
|
app.errPrintln(ArgumentExceptionFactory.adaptPropertyException(e, d).getMessageObject());
|
app.errPrintln();
|
}
|
}
|
}
|
}
|
|
/**
|
* The threshold above which choice menus should be displayed in multiple columns.
|
*/
|
private static final int MULTI_COLUMN_THRESHOLD = 8;
|
|
/** The application console. */
|
private final ConsoleApplication app;
|
|
/** The management context. */
|
private final ManagementContext context;
|
|
/**
|
* The modifications performed: we assume that at most there is one modification per property definition.
|
*/
|
private final List<PropertyEditorModification<?>> mods = new ArrayList<PropertyEditorModification<?>>();
|
|
/**
|
* Whether the last type of choice made by the user in a menu is a reset.
|
*/
|
private boolean isLastChoiceReset;
|
|
/**
|
* Create a new property value editor which will read from the provided application console.
|
*
|
* @param app
|
* The application console.
|
* @param context
|
* The management context.
|
*/
|
public PropertyValueEditor(ConsoleApplication app, ManagementContext context) {
|
this.app = app;
|
this.context = context;
|
}
|
|
/**
|
* Interactively edits the properties of a managed object. Only the properties listed in the provided collection
|
* will be accessible to the client. It is up to the caller to ensure that the list of properties does not include
|
* read-only, monitoring, hidden, or advanced properties as appropriate.
|
*
|
* @param mo
|
* The managed object.
|
* @param c
|
* The collection of properties which can be edited.
|
* @param isCreate
|
* Flag indicating whether or not the managed object is being created. If it is then read-only properties
|
* will be modifiable.
|
* @return Returns {@code MenuResult.success()} if the changes made to the managed object should be applied, or
|
* {@code MenuResult.cancel()} if the user to chose to cancel any changes, or {@code MenuResult.quit()} if
|
* the user chose to quit the application.
|
* @throws ClientException
|
* If the user input could not be retrieved for some reason.
|
*/
|
public MenuResult<Void> edit(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c, boolean isCreate)
|
throws ClientException {
|
|
// Get values for this missing mandatory property.
|
for (PropertyDefinition<?> pd : c) {
|
if (pd.hasOption(PropertyOption.MANDATORY) && mo.getPropertyValues(pd).isEmpty()) {
|
MandatoryPropertyInitializer mpi = new MandatoryPropertyInitializer(mo, pd);
|
MenuResult<Void> result = mpi.invoke(app);
|
if (!result.isSuccess()) {
|
return result;
|
}
|
}
|
}
|
|
while (true) {
|
// Construct the main menu.
|
MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
|
|
String ufn = mo.getManagedObjectPath().getName();
|
if (ufn == null) {
|
ufn = mo.getManagedObjectDefinition().getUserFriendlyName().toString();
|
}
|
builder.setPrompt(INFO_EDITOR_HEADING_CONFIGURE_COMPONENT.get(ufn));
|
|
LocalizableMessage heading1 = INFO_DSCFG_HEADING_PROPERTY_NAME.get();
|
LocalizableMessage heading2 = INFO_DSCFG_HEADING_PROPERTY_VALUE.get();
|
builder.setColumnHeadings(heading1, heading2);
|
builder.setColumnWidths(null, 0);
|
|
// Create an option for editing/viewing each property.
|
for (PropertyDefinition<?> pd : c) {
|
// Determine whether this property should be modifiable.
|
boolean isReadOnly = pd.hasOption(PropertyOption.MONITORING);
|
if (!isCreate && pd.hasOption(PropertyOption.READ_ONLY)) {
|
isReadOnly = true;
|
}
|
|
// Create the appropriate property action.
|
MenuCallback<Boolean> callback;
|
if (isReadOnly) {
|
callback = new ReadOnlyPropertyViewer(mo, pd);
|
} else if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
|
callback = new MultiValuedPropertyEditor(mo, pd);
|
} else {
|
callback = new SingleValuedPropertyEditor(mo, pd);
|
}
|
|
// Create the numeric option.
|
LocalizableMessage values = getPropertyValues(pd, mo);
|
builder.addNumberedOption(LocalizableMessage.raw("%s", pd.getName()), callback, values);
|
}
|
|
// Add a help option which displays a summary of the managed
|
// object's definition.
|
HelpCallback helpCallback = new ComponentHelpCallback(mo, c);
|
builder.addHelpOption(helpCallback);
|
|
// Add an option to apply the changes.
|
if (isCreate) {
|
builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
|
INFO_EDITOR_OPTION_FINISH_CREATE_COMPONENT.get(ufn), MenuResult.success(true));
|
} else {
|
builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
|
INFO_EDITOR_OPTION_FINISH_MODIFY_COMPONENT.get(ufn), MenuResult.success(true));
|
}
|
|
builder.setDefault(INFO_EDITOR_OPTION_FINISH_KEY.get(), MenuResult.success(true));
|
|
// Add options for canceling and quitting.
|
if (app.isMenuDrivenMode()) {
|
builder.addCancelOption(false);
|
}
|
builder.addQuitOption();
|
|
// Run the menu - success indicates that any changes should be
|
// committed.
|
app.println();
|
app.println();
|
Menu<Boolean> menu = builder.toMenu();
|
MenuResult<Boolean> result = menu.run();
|
|
if (result.isSuccess()) {
|
if (result.getValue()) {
|
return MenuResult.success();
|
}
|
} else if (result.isCancel()) {
|
return MenuResult.cancel();
|
} else {
|
return MenuResult.quit();
|
}
|
}
|
}
|
|
/**
|
* Register the modification in the list of modifications.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @param newValues
|
* the resulting values of the property once the modification is applied.
|
* @param previousValues
|
* the values we had before the modification is applied (these are not necessarily the *original* values
|
* if we already have other modifications applied to the same property).
|
*/
|
private <T> void registerModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
|
SortedSet<T> previousValues) {
|
|
if (isLastChoiceReset) {
|
registerResetModification(pd, previousValues);
|
} else if (!newValues.equals(previousValues)) {
|
if (newValues.containsAll(previousValues)) {
|
if (newValues.size() <= 1) {
|
registerSetModification(pd, newValues, previousValues);
|
} else {
|
registerAddModification(pd, newValues, previousValues);
|
}
|
} else if (previousValues.containsAll(newValues)) {
|
registerRemoveModification(pd, newValues, previousValues);
|
} else if (newValues.size() <= 1) {
|
registerSetModification(pd, newValues, previousValues);
|
} else {
|
// Split into two operations: remove and add
|
SortedSet<T> removedValues = new TreeSet<T>(previousValues);
|
removedValues.removeAll(newValues);
|
|
PropertyEditorModification<T> removeMod = PropertyEditorModification.createRemoveModification(pd,
|
removedValues, previousValues);
|
addModification(removeMod);
|
|
SortedSet<T> retainedValues = new TreeSet<T>(previousValues);
|
retainedValues.retainAll(newValues);
|
|
SortedSet<T> addedValues = new TreeSet<T>(newValues);
|
addedValues.removeAll(retainedValues);
|
|
PropertyEditorModification<T> addMod = PropertyEditorModification.createAddModification(pd,
|
addedValues, retainedValues);
|
addModification(addMod);
|
}
|
}
|
}
|
|
/**
|
* Register a reset modification in the list of modifications.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @param previousValues
|
* the values we had before the modification is applied (these are not necessarily the *original* values
|
* if we already have other modifications applied to the same property).
|
*/
|
private <T> void registerResetModification(PropertyDefinition<T> pd, SortedSet<T> previousValues) {
|
PropertyEditorModification<?> mod = getModification(pd);
|
SortedSet<T> originalValues;
|
if (mod != null) {
|
originalValues = new TreeSet<T>(pd);
|
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
|
removeModification(mod);
|
} else {
|
originalValues = new TreeSet<T>(previousValues);
|
}
|
|
addModification(PropertyEditorModification.createResetModification(pd, originalValues));
|
}
|
|
/**
|
* Register a set modification in the list of modifications.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @param newValues
|
* the resulting values of the property once the modification is applied.
|
* @param previousValues
|
* the values we had before the modification is applied (these are not necessarily the *original* values
|
* if we already have other modifications applied to the same property).
|
*/
|
private <T> void registerSetModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
|
SortedSet<T> previousValues) {
|
PropertyEditorModification<?> mod = getModification(pd);
|
SortedSet<T> originalValues;
|
if (mod != null) {
|
originalValues = new TreeSet<T>(pd);
|
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
|
removeModification(mod);
|
} else {
|
originalValues = new TreeSet<T>(previousValues);
|
}
|
addModification(PropertyEditorModification.createSetModification(pd, newValues, originalValues));
|
}
|
|
/**
|
* Register an add modification in the list of modifications.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @param newValues
|
* the resulting values of the property once the modification is applied.
|
* @param previousValues
|
* the values we had before the modification is applied (these are not necessarily the *original* values
|
* if we already have other modifications applied to the same property).
|
*/
|
private <T> void registerAddModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
|
SortedSet<T> previousValues) {
|
PropertyEditorModification<?> mod = getModification(pd);
|
PropertyEditorModification<T> newMod;
|
SortedSet<T> originalValues;
|
if (mod != null) {
|
originalValues = new TreeSet<T>(pd);
|
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
|
if (mod.getType() == PropertyEditorModification.Type.ADD) {
|
SortedSet<T> addedValues = new TreeSet<T>(newValues);
|
addedValues.removeAll(originalValues);
|
newMod = PropertyEditorModification.createAddModification(pd, addedValues, originalValues);
|
} else {
|
newMod = PropertyEditorModification
|
.createSetModification(pd, new TreeSet<T>(newValues), originalValues);
|
}
|
removeModification(mod);
|
} else {
|
originalValues = new TreeSet<T>(previousValues);
|
SortedSet<T> addedValues = new TreeSet<T>(newValues);
|
addedValues.removeAll(originalValues);
|
newMod = PropertyEditorModification.createAddModification(pd, addedValues, originalValues);
|
}
|
addModification(newMod);
|
}
|
|
/**
|
* Register a remove modification in the list of modifications.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @param newValues
|
* the resulting values of the property once the modification is applied.
|
* @param previousValues
|
* the values we had before the modification is applied (these are not necessarily the *original* values
|
* if we already have other modifications applied to the same property).
|
*/
|
private <T> void registerRemoveModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
|
SortedSet<T> previousValues) {
|
PropertyEditorModification<?> mod = getModification(pd);
|
PropertyEditorModification<T> newMod;
|
SortedSet<T> originalValues;
|
if (mod != null) {
|
originalValues = new TreeSet<T>(pd);
|
castAndAddValues(originalValues, mod.getOriginalValues(), pd);
|
if (newValues.isEmpty()) {
|
newMod = PropertyEditorModification.createRemoveModification(pd, originalValues, originalValues);
|
} else if (mod.getType() == PropertyEditorModification.Type.REMOVE) {
|
SortedSet<T> removedValues = new TreeSet<T>(originalValues);
|
removedValues.removeAll(newValues);
|
newMod = PropertyEditorModification.createRemoveModification(pd, removedValues, originalValues);
|
} else {
|
newMod = PropertyEditorModification
|
.createSetModification(pd, new TreeSet<T>(newValues), originalValues);
|
}
|
removeModification(mod);
|
} else {
|
originalValues = new TreeSet<T>(previousValues);
|
SortedSet<T> removedValues = new TreeSet<T>(originalValues);
|
removedValues.removeAll(newValues);
|
newMod = PropertyEditorModification.createRemoveModification(pd, removedValues, originalValues);
|
}
|
addModification(newMod);
|
}
|
|
/**
|
* Returns the modifications that have been applied during the last call of the method PropertyValueEditor.edit.
|
*
|
* @return the modifications that have been applied during the last call of the method PropertyValueEditor.edit.
|
*/
|
public Collection<PropertyEditorModification<?>> getModifications() {
|
return mods;
|
}
|
|
/**
|
* Clears the list of modifications.
|
*/
|
public void resetModifications() {
|
mods.clear();
|
}
|
|
/**
|
* Adds a modification to the list of modifications that have been performed.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param mod
|
* the modification to be added.
|
*/
|
private <T> void addModification(PropertyEditorModification<T> mod) {
|
mods.add(mod);
|
}
|
|
/**
|
* Removes a modification from the list of modifications that have been performed.
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param mod
|
* the modification to be removed.
|
*/
|
private <T> boolean removeModification(PropertyEditorModification<T> mod) {
|
return mods.remove(mod);
|
}
|
|
/**
|
* Returns the modification associated with a given property definition: we assume that we have only one
|
* modification per property definition (in the worst case we merge the modifications and generate a unique set
|
* modification).
|
*
|
* @param <T>
|
* The type of the underlying property associated with the modification.
|
* @param pd
|
* the property definition.
|
* @return the modification associated with the provided property definition and <CODE>null</CODE> if no
|
* modification could be found.
|
*/
|
private <T> PropertyEditorModification<?> getModification(PropertyDefinition<T> pd) {
|
PropertyEditorModification<?> mod = null;
|
|
for (PropertyEditorModification<?> m : mods) {
|
if (pd.equals(m.getPropertyDefinition())) {
|
mod = m;
|
break;
|
}
|
}
|
|
return mod;
|
}
|
|
/**
|
* This method is required to avoid compilation warnings. It basically adds the contents of a collection to another
|
* collection by explicitly casting its values. This is done because the method getModification() returns au
|
* undefined type.
|
*
|
* @param <T>
|
* The type of the destination values.
|
* @param destination
|
* the collection that we want to update.
|
* @param source
|
* the collection whose values we want to add (and cast) to the source collection.
|
* @param pd
|
* the PropertyDefinition we use to do the casting.
|
* @throws ClassCastException
|
* if an error occurs during the cast of the objects.
|
*/
|
private <T> void castAndAddValues(Collection<T> destination, Collection<?> source, PropertyDefinition<T> pd) {
|
for (Object o : source) {
|
destination.add(pd.castValue(o));
|
}
|
}
|
}
|