/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.tools.dsconfig; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.messages.ToolMessages.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider; import org.opends.server.admin.AliasDefaultBehaviorProvider; import org.opends.server.admin.BooleanPropertyDefinition; import org.opends.server.admin.DefaultBehaviorProviderVisitor; import org.opends.server.admin.DefinedDefaultBehaviorProvider; import org.opends.server.admin.EnumPropertyDefinition; import org.opends.server.admin.IllegalPropertyValueStringException; import org.opends.server.admin.PropertyDefinition; import org.opends.server.admin.PropertyDefinitionVisitor; import org.opends.server.admin.PropertyOption; import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider; import org.opends.server.admin.UndefinedDefaultBehaviorProvider; import org.opends.server.admin.UnknownPropertyDefinitionException; import org.opends.server.admin.client.ManagedObject; import org.opends.server.tools.ClientException; import org.opends.server.util.args.ArgumentException; import org.opends.server.util.table.TableBuilder; import org.opends.server.util.table.TextTablePrinter; /** * A class responsible for interactively retrieving property values * from the console. */ final class PropertyValueReader { /** * A help call-back which displays help on a property. */ private static final class PropertyHelpCallback implements HelpCallback { // The managed object. private final ManagedObject mo; // The property definition. private final PropertyDefinition pd; /** * Creates a new property help call-back. * * @param mo * The managed object. * @param pd * The property definition. */ public PropertyHelpCallback(ManagedObject mo, PropertyDefinition pd) { this.mo = mo; this.pd = pd; } /** * {@inheritDoc} */ public void display(ConsoleApplication app) { app.println(); HelpSubCommandHandler.displayVerboseSingleProperty(mo .getManagedObjectDefinition(), pd.getName(), app.getErrorStream()); } } /** * A menu call-back used for editing property values. */ private interface MenuCallback { /** * Invoke the menu call-back. * * @param mo * The managed object. * @param pd * The property definition to be modified. * @param * The type of property to be edited. * @throws ArgumentException * If the user input could not be retrieved for some * reason. */ void invoke(ManagedObject mo, PropertyDefinition pd) throws ArgumentException; } /** * A menu call-back for adding values to a property. */ private final class AddValueMenuCallback implements MenuCallback { /** * {@inheritDoc} */ public void invoke(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { // TODO: display error if the value already exists. // TODO: for enumerations, only display the values which are not // already assigned. T value = read(mo, pd); SortedSet values = mo.getPropertyValues(pd); values.add(value); mo.setPropertyValues(pd, values); } } /** * A menu call-back for removing values from a property. */ private final class RemoveValueMenuCallback implements MenuCallback { /** * {@inheritDoc} */ public void invoke(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { PropertyValuePrinter printer = new PropertyValuePrinter(null, null, false); SortedSet values = mo.getPropertyValues(pd); List descriptions = new ArrayList(values.size()); List lvalues = new ArrayList(values.size()); for (T value : values) { descriptions.add(printer.print(pd, value)); lvalues.add(value); } String promptMsg = getMessage(MSGID_DSCFG_VALUE_READER_PROMPT_REMOVE, pd.getName()); T value = app.readChoice(promptMsg, descriptions, lvalues, null); values.remove(value); mo.setPropertyValues(pd, values); } } /** * A menu call-back for resetting a property back to its defaults. */ private final class ResetValueMenuCallback implements MenuCallback { /** * {@inheritDoc} */ public void invoke(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { mo.setPropertyValue(pd, null); } } /** * A menu call-back for setting a single-valued property. */ private final class SetValueMenuCallback implements MenuCallback { /** * {@inheritDoc} */ public void invoke(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { T value = read(mo, pd); mo.setPropertyValue(pd, value); } } /** * The reader implementation. */ private final class Visitor extends PropertyDefinitionVisitor { // Any argument exception that was caught during processing. private ArgumentException ae = null; /** * Read a value from the console for the provided property * definition. * * @param pd * The property definition. * @return Returns the string value. * @throws ArgumentException * If the user input could not be retrieved for some * reason. */ public String read(PropertyDefinition pd) throws ArgumentException { String result = pd.accept(this, null); if (result != null) { return result; } else if (ae != null) { throw ae; } else { throw new IllegalStateException( "No result and no ArgumentException caught"); } } /** * {@inheritDoc} */ @Override public String visitBoolean(BooleanPropertyDefinition d, Void p) { List values = Arrays.asList(new String[] { "false", "true" }); try { String promptMsg = getMessage( MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE, d.getName()); return app.readChoice(promptMsg, values, values, null); } catch (ArgumentException e) { ae = e; return null; } } /** * {@inheritDoc} */ @Override public > String visitEnum(EnumPropertyDefinition d, Void p) { SortedMap map = new TreeMap(); for (E value : EnumSet.allOf(d.getEnumClass())) { String s = String.format("%s : %s", value.toString(), d .getValueSynopsis(value)); map.put(value.toString(), s); } List descriptions = new ArrayList(map.values()); List values = new ArrayList(map.keySet()); try { String promptMsg = getMessage( MSGID_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE, d.getName()); return app.readChoice(promptMsg, descriptions, values, null); } catch (ArgumentException e) { ae = e; return null; } } /** * {@inheritDoc} */ @Override public String visitUnknown(PropertyDefinition d, Void p) throws UnknownPropertyDefinitionException { try { String promptMsg = getMessage( MSGID_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE, d.getName()); return app.readLineOfInput(promptMsg); } catch (ArgumentException e) { ae = e; return null; } } } // The application console. private final ConsoleApplication app; /** * Create a new property value reader which will read from the * provider application console. * * @param app * The application console. */ public PropertyValueReader(ConsoleApplication app) { this.app = app; } /** * Asks the user to input a single value for the provided property * definition. The value will be validated according to the * constraints of the property definition and its decoded value * returned. * * @param * The underlying type of the property definition. * @param mo * The managed object. * @param pd * The property definition. * @return Returns the validated string value. * @throws ArgumentException * If the user input could not be retrieved for some * reason. */ public T read(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { while (true) { app.println(); String value = pd.accept(new Visitor(), null); try { return pd.decodeValue(value); } catch (IllegalPropertyValueStringException e) { app.println(); app.printMessage(ArgumentExceptionFactory.adaptPropertyException(e, mo.getManagedObjectDefinition()).getMessage()); } } } /** * Edit 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. * @throws ArgumentException * If the user input could not be retrieved for some * reason. */ public void readAll(ManagedObject mo, Collection> c) throws ArgumentException { // Get values for this missing mandatory property. for (PropertyDefinition pd : c) { if (pd.hasOption(PropertyOption.MANDATORY)) { if (mo.getPropertyValues(pd).isEmpty()) { editProperty(mo, pd); } } } // Now let users modify remaining properties. boolean isFinished = false; PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null, null, false); while (!isFinished) { // Display a menu allowing users to edit individual options. TableBuilder builder = new TableBuilder(); builder.appendHeading(); builder.appendHeading(getMessage(MSGID_DSCFG_HEADING_PROPERTY_NAME)); builder.appendHeading(getMessage(MSGID_DSCFG_HEADING_PROPERTY_VALUE)); int i = 0; List> pl = new ArrayList>(c); for (PropertyDefinition pd : pl) { builder.startRow(); builder.appendCell("[" + i + "]"); builder.appendCell(pd.getName()); String values = getPropertyValuesAsString(mo, pd, valuePrinter); builder.appendCell(values); i++; } builder.startRow(); builder.startRow(); builder.appendCell("[" + i + "]"); builder.appendCell(getMessage(MSGID_DSCFG_VALUE_READER_MENU_CONTINUE)); // Display the menu. app.println(); app.printMessage(getMessage(MSGID_DSCFG_VALUE_READER_MENU_TITLE, i)); app.println(); TextTablePrinter printer = new TextTablePrinter(app.getErrorStream()); printer.setColumnWidth(2, 0); builder.print(printer); // Get the user input. final int size = i; String promptMsg = getMessage(MSGID_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP, i); ValidationCallback validator = new ValidationCallback() { public Integer validate(ConsoleApplication app, String input) { String ninput = input.trim(); try { int j = Integer.parseInt(ninput); if (j < 1 || j > size) { throw new NumberFormatException(); } return j; } catch (NumberFormatException e) { app.println(); String errMsg = getMessage(MSGID_DSCFG_ERROR_GENERAL_CHOICE, size); app.printMessage(errMsg); return null; } } }; // Get the choice. int choice; try { choice = app.readValidatedInput(promptMsg, validator); } catch (ClientException e) { // Should never happen. throw new RuntimeException(e); } if (choice == size) { isFinished = true; } else { editProperty(mo, pl.get(choice)); } } } // Interactively edit a property. private void editProperty(ManagedObject mo, PropertyDefinition pd) throws ArgumentException { // If the property is mandatory then make sure we prompt for an // initial value. if (pd.hasOption(PropertyOption.MANDATORY)) { if (mo.getPropertyValues(pd).isEmpty()) { app.println(); String promptMsg = getMessage( MSGID_DSCFG_VALUE_READER_PROMPT_MANDATORY, pd.getName()); app.printMessage(promptMsg); T value = read(mo, pd); mo.setPropertyValue(pd, value); } } boolean isFinished = false; while (!isFinished) { // Construct a list of menu options and their call-backs. List descriptions = new ArrayList(); List callbacks = new ArrayList(); if (pd.hasOption(PropertyOption.MULTI_VALUED)) { descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_ADD)); callbacks.add(new AddValueMenuCallback()); if (!mo.getPropertyValues(pd).isEmpty()) { descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_REMOVE)); callbacks.add(new RemoveValueMenuCallback()); } } else { descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_SET)); callbacks.add(new SetValueMenuCallback()); } if (!pd.hasOption(PropertyOption.MANDATORY) || !(pd.getDefaultBehaviorProvider() instanceof UndefinedDefaultBehaviorProvider)) { descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_RESET)); callbacks.add(new ResetValueMenuCallback()); } descriptions.add(getMessage(MSGID_DSCFG_VALUE_READER_MENU_CONTINUE)); callbacks.add(null); // FIXME: display current values of the property. String promptMsg = getMessage( MSGID_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU, pd.getName()); MenuCallback callback = app.readChoice(promptMsg, descriptions, callbacks, new PropertyHelpCallback(mo, pd)); if (callback != null) { callback.invoke(mo, pd); } else { isFinished = true; } } } // Display the set of values associated with a property. private String getPropertyValuesAsString(ManagedObject mo, PropertyDefinition pd, PropertyValuePrinter valuePrinter) { SortedSet values = mo.getPropertyValues(pd); if (values.isEmpty()) { // There are no values or default values. Display the default // behavior for alias values. DefaultBehaviorProviderVisitor visitor = new DefaultBehaviorProviderVisitor() { public String visitAbsoluteInherited( AbsoluteInheritedDefaultBehaviorProvider d, Void p) { // Should not happen - inherited default values are // displayed as normal values. throw new IllegalStateException(); } public String visitAlias(AliasDefaultBehaviorProvider d, Void p) { if (app.isVerbose()) { return d.getSynopsis(); } else { return null; } } public String visitDefined( DefinedDefaultBehaviorProvider d, Void p) { // Should not happen - real default values are displayed as // normal values. throw new IllegalStateException(); } public String visitRelativeInherited( RelativeInheritedDefaultBehaviorProvider d, Void p) { // Should not happen - inherited default values are // displayed as normal values. throw new IllegalStateException(); } public String visitUndefined(UndefinedDefaultBehaviorProvider d, Void p) { return null; } }; String content = pd.getDefaultBehaviorProvider().accept(visitor, null); if (content == null) { return "-"; } else { return content; } } else { StringBuilder sb = new StringBuilder(); boolean isFirst = true; for (T value : values) { if (!isFirst) { sb.append(", "); } sb.append(valuePrinter.print(pd, value)); isFirst = false; } return sb.toString(); } } }