Package com.sun.enterprise.admin.cli

Source Code of com.sun.enterprise.admin.cli.CLICommand

/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License").  You
* may not use this file except in compliance with the License.  You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt.  See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license."  If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above.  However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/

package com.sun.enterprise.admin.cli;

import java.io.*;
import java.util.*;
import java.lang.annotation.Annotation;
import java.util.logging.*;

import org.jvnet.hk2.annotations.Contract;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.config.InjectionManager;
import org.jvnet.hk2.config.InjectionResolver;
import org.jvnet.hk2.config.UnsatisfiedDependencyException;

import org.glassfish.api.Param;
import org.glassfish.api.admin.*;
import org.glassfish.api.admin.CommandModel.ParamModel;
import org.glassfish.common.util.admin.CommandModelImpl;
import org.glassfish.common.util.admin.MapInjectionResolver;
import org.glassfish.common.util.admin.ManPageFinder;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.hk2.api.ServiceLocator;

import com.sun.enterprise.admin.util.CommandModelData.ParamModelData;
import com.sun.enterprise.admin.cli.remote.RemoteCommand;
import com.sun.enterprise.admin.util.LineTokenReplacer;
import com.sun.enterprise.admin.util.TokenValue;
import com.sun.enterprise.admin.util.TokenValueSet;
import com.sun.enterprise.universal.i18n.LocalStringsImpl;
import com.sun.enterprise.universal.glassfish.ASenvPropertyReader;
import com.sun.appserv.server.util.Version;
import com.sun.enterprise.admin.cli.remote.RemoteCLICommand;
import com.sun.enterprise.admin.remote.Metrix;

import javax.inject.Inject;
import javax.inject.Scope;
import javax.inject.Singleton;


/**
* Base class for a CLI command.  An instance of a subclass of this
* class is created using the getCommand method with the name of the
* command and the information about its environment.
* <p>
* A command is executed with a list of arguments using the execute
* method.  The implementation of the execute method in this class
* saves the arguments in the protected argv field, then calls the
* following protected methods in order: prepare, parse, validate,
* and executeCommand.  A subclass must implement the prepare method
* to initialize the metadata that specified the valid options for
* the command, and the executeCommand method to actually perform the
* command.  The parse and validate method may also be overridden if
* needed.  Or, the subclass may override the execute method and
* provide the complete implementation for the command, including
* option parsing.
*
* @author Bill Shannon
*/
@Contract
@PerLookup
public abstract class CLICommand implements PostConstruct {
    public static final int ERROR = CLIConstants.ERROR;
    public static final int CONNECTION_ERROR = 2;
    public static final int INVALID_COMMAND_ERROR = 3;
    public static final int SUCCESS = CLIConstants.SUCCESS;
    public static final int WARNING = CLIConstants.WARNING;

    private static final Set<String> unsupported;
    private static final String UNSUPPORTED_CMD_FILE_NAME =
                                    "unsupported-legacy-command-names";
    private static final String PACKAGE_NAME = "com.sun.enterprise.admin.cli";

    private static final LocalStringsImpl strings =
            new LocalStringsImpl(CLICommand.class);

    private static final Map<String,String> systemProps =
            Collections.unmodifiableMap(new ASenvPropertyReader().getProps());

    protected static final Logger logger =
            Logger.getLogger(CLICommand.class.getPackage().getName());

    // InjectionManager is completely stateless with only one method that
    // operates on its arguments, so we can share a single instance.
    private static final InjectionManager injectionMgr = new InjectionManager();

    private static String commandScope = null;

    // tokens that are substituted in manual pages
    // the tokens are delimited with {}
    // the tokens and tokenValues arrays must be kept in sync.  See the
    // expandManPage method for where the tokenValues are assigned.
    private static String manpageTokens[] = {
        "cname",            // the command name
        "cprefix",          // the environment variable prefix
        "product---name",   // the product name
    };
    private String manpageTokenValues[] = new String[manpageTokens.length];


    /**
     * The name of the command.
     * Initialized in the constructor.
     */
    protected String name;

    /**
     * The program options for the command.
     * Initialized in the constructor.
     */
    @Inject
    protected ProgramOptions programOpts;

    /**
     * The environment for the command.
     * Initialized in the constructor.
     */
    @Inject
    protected Environment env;

    /**
     * The command line arguments for this execution.
     * Initialized in the execute method.
     */
    protected String[] argv;

    /**
     * The metadata describing the command's options and operands.
     */
    protected CommandModel commandModel;

    protected StringBuilder metadataErrors;

    /**
     * The options parsed from the command line.
     * Initialized by the parse method.  The keys
     * are the parameter names from the command model,
     * not the "forced to all lower case" names that
     * are presented to the user.
     */
    protected ParameterMap options;

    /**
     * The operands parsed from the command line.
     * Initialized by the parse method.
     */
    protected List<String> operands;

    /**
     * The passwords read from the password file.
     * Initialized by the initializeCommandPassword method.
     */
    protected Map<String, String> passwords;
   
    static {
        Set<String> unsup = new HashSet<String>();
        file2Set(UNSUPPORTED_CMD_FILE_NAME, unsup);
        unsupported = Collections.unmodifiableSet(unsup);
    }
   
    private static boolean useRest() {
        //return environment != null && environment.getBooleanOption("USE_REST");
        return true;
    }

    /**
     * Get a CLICommand object representing the named command.
     */
    public static CLICommand getCommand(ServiceLocator serviceLocator, String name)
            throws CommandException {
        // first, check if it's a known unsupported command
        checkUnsupportedLegacyCommand(name);
        // next, try to load our own implementation of the command
        ProgramOptions po = serviceLocator.getService(ProgramOptions.class);
        CLICommand cmd = serviceLocator.getService(CLICommand.class, name);
        if (cmd != null) {
            po.removeDetach();
            return cmd;
        }
        // nope, must be a remote command
        logger.finer("Assuming it's a remote command: " + name);
        return getRemoteCommand(name, po, serviceLocator.getService(Environment.class));
    }
   
    public static CLICommand getCommand(CLIContainer cLIContainer, String name)
            throws CommandException {
        // first, check if it's a known unsupported command
        checkUnsupportedLegacyCommand(name);
        // next, try to load our own implementation of the command
        ProgramOptions po = cLIContainer.getProgramOptions();
        CLICommand cmd = cLIContainer.getLocalCommand(name);
        if (cmd != null) {
            po.removeDetach();
            return cmd;
        }
        // nope, must be a remote command
        if (logger.isLoggable(Level.FINER)) {
            logger.finer("Assuming it's a remote command: " + name);
        }
        return getRemoteCommand(name, po, cLIContainer.getEnvironment());
    }
   
    private static CLICommand getRemoteCommand(String name, ProgramOptions po, Environment env) throws CommandException {
        if (useRest()) {
            return new RemoteCLICommand(name, po, env);
        } else {
            return new RemoteCommand(name, po, env);
        }
    }

    /**
     * Constructor used by subclasses when instantiated by HK2.
     * ProgramOptions and Environment are injected.  name is set here.
     */
    protected CLICommand() {
        Service service = this.getClass().getAnnotation(Service.class);

        if (service == null)
            name = "unknown-command";   // should never happen
        else
            name = service.name();
    }

    /**
     * Initialize the logger after being instantiated by HK2.
     */
    public void postConstruct() {
        initializeLogger();
    }

    /**
     * Constructor used by subclasses to save the name, program options,
     * and environment information into corresponding protected fields.
     * Finally, this constructor calls the initializeLogger method.
     */
    protected CLICommand(String name, ProgramOptions programOpts,
            Environment env) {
        this.name = name;
        this.programOpts = programOpts;
        this.env = env;
        initializeLogger();
    }

    /**
     * Execute this command with the given arguemnts.
     * The implementation in this class saves the passed arguments in
     * the argv field and calls the initializePasswords method.
     * Then it calls the prepare, parse, and validate methods, finally
     * returning the result of calling the executeCommand method.
     * Note that argv[0] is the command name.
     *
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    public int execute(String... argv) throws CommandException {
        this.argv = argv;
        initializePasswords();
        logger.finer("Prepare");
        prepare();
        logger.finer("Process program options");
        processProgramOptions();
        logger.finer("Parse command options");
        parse();
        if (checkHelp())
            return 0;
        logger.finer("Prevalidate command options");
        prevalidate();
        logger.finer("Inject command options");
        inject();
        logger.finer("Validate command options");
        validate();
        if (programOpts.isEcho()) {
            logger.info(echoCommand());
            // In order to avoid echoing commands used intenally to the
            // implementation of *this* command, we turn off echo after
            // having echoed this command.
            programOpts.setEcho(false);
        } else if (logger.isLoggable(Level.FINER))
            logger.finer(echoCommand());
        logger.finer("Execute command");
        return executeCommand();
    }

    /**
     * Return the name of this command.
     */
    public String getName() {
        return name;
    }
   
    /*
     * Return the command scope for this command.  The command scope is
     * a name space in which commands are defined. Command clients can specify a scope
     * to use in looking up a command. Currently this is only used for remote
     * commands. By default, the context is null.
     */
    public static String getCommandScope() {
        return commandScope;
    }

    /*
     * Set the command scope for this command. 
     */
    public static void setCommandScope(String ctx) {
        commandScope = ctx;
    }

    /**
     * Returns the program options associated with this command.
     *
     * @return the command's program options
     */
    public ProgramOptions getProgramOptions() {
        return programOpts;
    }

    /**
     * Return a BufferedReader for the man page for this command,
     * or null if not found.
     */
    public BufferedReader getManPage() {
        String commandName = getName();
        if (commandName.length() == 0)
            throw new IllegalArgumentException("Command name cannot be empty");

        // special case "help" --> help for the command
        if (commandName.equals("help"))
            commandName = programOpts.getCommandName();

        return ManPageFinder.getCommandManPage(
                                commandName,
                                getClass().getName(),
                                Locale.getDefault(),
                                getClass().getClassLoader(),
                                logger);
    }
   
    /**
     * Return a man page for this command that has the tokens substituted
     */
    public BufferedReader expandManPage(Reader r) {
        manpageTokenValues[0] = programOpts.getCommandName();
        manpageTokenValues[1] = Environment.getPrefix();
        manpageTokenValues[2] = Version.getBriefProductName();
        TokenValueSet tvs = new TokenValueSet();
        for (int i = 0; i < manpageTokens.length; i++) {
            tvs.add(new TokenValue(manpageTokens[i], manpageTokenValues[i], "{", "}"));
        }
        return new BufferedReader(new LineTokenReplacer(tvs).getReader(r));
    }

    /**
     * Get the usage text for the subcommand. This method shows the details for
     * the subcommand options but does not provide details about the command
     * options.
     *
     * @return usage text
     */
    public String getUsage() {
        String usage;
        if (commandModel != null && ok(usage = commandModel.getUsageText())) {
            StringBuffer usageText = new StringBuffer();
            usageText.append(
                strings.get("Usage", strings.get("Usage.brief", programOpts.getCommandName())));
            usageText.append(" ");
            usageText.append(usage);
            return usageText.toString();
        } else {
            return generateUsageText();
        }
    }

    private String generateUsageText() {
        StringBuilder usageText = new StringBuilder();
        usageText.append(strings.get("Usage", strings.get("Usage.brief", programOpts.getCommandName())));
        usageText.append(" ");
        usageText.append(getName());
        int len = usageText.length();
        StringBuilder optText = new StringBuilder();
        String lsep = System.getProperty("line.separator");
        for (ParamModel opt : usageOptions()) {
            optText.setLength(0);
            final String optName = lc(opt.getName());
            // "--terse" is part of asadmin utility options
            if (optName.equals("terse"))
                continue;
            // skip "hidden" options
            if (optName.startsWith("_"))
                continue;
            // do not want to display password as an option
            if (opt.getParam().password())
                continue;
            // also do not want to display obsolete options
            if (opt.getParam().obsolete())
                continue;
            // primary parameter is the operand, not an option
            if (opt.getParam().primary())
                continue;
            boolean optional = opt.getParam().optional();
            String defValue = opt.getParam().defaultValue();
            if (optional)
                optText.append("[");
            String sn = opt.getParam().shortName();
            if (ok(sn))
                optText.append('-').append(sn).append('|');
            optText.append("--").append(optName);

            if (opt.getType() == Boolean.class ||
                opt.getType() == boolean.class) {
                // canonicalize default value
                if (ok(defValue) && Boolean.parseBoolean(defValue))
                    defValue = "true";
                else
                    defValue = "false";
                optText.append("[=<").append(optName);
                optText.append(strings.get("Usage.default", defValue));
                optText.append(">]");
            } else {    // STRING or FILE
                if (ok(defValue)) {
                    optText.append(" <").append(optName);
                    optText.append(strings.get("Usage.default", defValue));
                    optText.append('>');
                } else
                    optText.append(" <").append(optName).append('>');
            }
            if (optional)
                optText.append("]");

            if (len + 1 + optText.length() > 80) {
                usageText.append(lsep).append('\t');
                len = 8;
            } else {
                usageText.append(' ');
                len++;
            }
            usageText.append(optText);
            len += optText.length();
        }

        // add --help text
        String helpText = "[-?|--help[=<help(default:false)>]]";
        if (len + 1 + helpText.length() > 80) {
            usageText.append(lsep).append('\t');
            len = 8;
        } else {
            usageText.append(' ');
            len++;
        }
        usageText.append(helpText);
        len += helpText.length();

        optText.setLength(0);
        ParamModel operandParam = getOperandModel();
        String opname = operandParam != null ?
        lc(operandParam.getName()) : null;
        if (!ok(opname))
            opname = "operand";

        int operandMin = 0;
        int operandMax = 0;
        if (operandParam != null) {
            operandMin = operandParam.getParam().optional() ? 0 : 1;
            operandMax = operandParam.getParam().multiple() ?
                                                        Integer.MAX_VALUE : 1;
        }
        if (operandMax > 0) {
            if (operandMin == 0) {
                optText.append("[").append(opname);
                if (operandMax > 1)
                    optText.append(" ...");
                optText.append("]");
            } else {
                optText.append(opname);
                if (operandMax > 1)
                    optText.append(" ...");
            }
        }
        if (len + 1 + optText.length() > 80) {
            usageText.append(lsep).append('\t');
            len = 8;
        } else {
            usageText.append(' ');
            len++;
        }
        usageText.append(optText);
        return usageText.toString();
    }

    /**
     * Subclasses can override this method to supply additional
     * or different options that should be part of the usage text.
     * Most commands will never need to do this, but the create-domain
     * command uses it to include the --user option as a required option.
     */
    protected Collection<ParamModel> usageOptions() {
        return commandModel.getParameters();
    }
   
    /**
     * Get the usage text for the command.  This usage text shows the details
     * of the command options but does not show the details for the subcommand
     * options. The subcommand argument is used to fill in the subcommand name
     * in the usage text.
     * @return usage text for the command
     */
    public String getCommandUsage() {
        return strings.get("Usage.full", programOpts.getCommandName());
    }
   
    public String getBriefCommandUsage() {
        return strings.get("Usage.brief", programOpts.getCommandName());
    }

    @Override
    public String toString() {
        return echoCommand();
    }


    /**
     * Return a string representing the command line used with this command.
     */
    private String echoCommand() {
        StringBuilder sb = new StringBuilder();

        // first, the program options
        sb.append(programOpts.getCommandName());
        sb.append(' ');
        sb.append(programOpts.toString()).append(' ');

        // now the subcommand options and operands
        sb.append(name).append(' ');

        // have we parsed any options yet?
        if (options != null && operands != null) {
            for (ParamModel opt : commandModel.getParameters()) {
                if (opt.getParam().password())
                    continue;       // don't print passwords
                if (opt.getParam().primary())
                    continue;
                // include every option that was specified on the command line
                // and every option that has a default value                              
                if (opt.getParam().multiple()) {
                    List<String> paramValues = getOptions(opt.getName());
                    for (String v : paramValues) {
                        appendEchoOption(sb, opt, v);
                    }            
                } else {   
                    String value = getOption(opt.getName());
                    if (value != null) {
                        appendEchoOption(sb, opt, value);
                    }
                }
            }
            for (String o : operands)
                sb.append(quote(o)).append(' ');
        } else if (argv != null) {
            // haven't parsed any options, include raw arguments, if any
            for (String arg : argv)
                sb.append(quote(arg)).append(' ');
        }

        sb.setLength(sb.length() - 1)// strip trailing space
        return sb.toString();
    }
   
    private void appendEchoOption(StringBuilder sb, ParamModel opt, String value) {
        sb.append("--").append(lc(opt.getName()));
        if (opt.getType() == Boolean.class ||
            opt.getType() == boolean.class) {
            sb.append("=").append(Boolean.toString(Boolean.parseBoolean(value)));
        } else {    // STRING or FILE
            sb.append(" ").append(quote(value));
        }
        sb.append(' ');
    }

    /**
     * Quote a value, if the value contains any special characters.
     *
     * @param   value   value to be quoted
     * @return          the possibly quoted value
     */
    public static String quote(String value) {
        int len = value.length();
        if (len == 0)
            return "\"\"";      // an empty string is handled specially

        /*
         * Look for any special characters.  Escape and
         * quote the entire string if necessary.
         */
        boolean needQuoting = false;
        for (int i = 0; i < len; i++) {
            char c = value.charAt(i);
            if (c == '"' || c == '\\' || c == '\r' || c == '\n') {
                // need to escape them and then quote the whole string
                StringBuffer sb = new StringBuffer(len + 3);
                sb.append('"');
                sb.append(value.substring(0, i));
                int lastc = 0;
                for (int j = i; j < len; j++) {
                    char cc = value.charAt(j);
                    if ((cc == '"') || (cc == '\\') ||
                        (cc == '\r') || (cc == '\n'))
                        if (cc == '\n' && lastc == '\r')
                            ;   // do nothing, CR was already escaped
                        else
                            sb.append('\\');    // Escape the character
                    sb.append(cc);
                    lastc = cc;
                }
                sb.append('"');
                return sb.toString();
            } else if (c <= 040 || c >= 0177)
                // These characters cause the string to be quoted
                needQuoting = true;
        }

        if (needQuoting) {
            StringBuffer sb = new StringBuffer(len + 2);
            sb.append('"').append(value).append('"');
            return sb.toString();
        } else
            return value;
    }

    /**
     * If the program options haven't already been set, parse them
     * on the command line and remove them from the command line.
     * Subclasses should call this method in their prepare method
     * after initializing commandOpts (so usage is available on failure)
     * if they want to allow program options after the command name.
     * Currently RemoteCommand does this, as well as the local commands
     * that also need to talk to the server.
     */
    protected void processProgramOptions() throws CommandException {
        /*
         * asadmin options and command options are intermixed.
         * Parse the entire command line for asadmin options,
         * removing them from the command line, and ignoring
         * unknown options.
         */
        Collection<ParamModel> model = ProgramOptions.getValidOptions();
        if (programOpts.isOptionsSet()) {
            model = ProgramOptions.getHelpOption();
        }
        Parser rcp = new Parser(argv, 0, model, true);
        ParameterMap params = rcp.getOptions();
        List<String> oprds = rcp.getOperands();
        argv = oprds.toArray(new String[oprds.size()]);
        if (params.size() > 0) {
            // at least one program option specified after command name
            logger.finer("Update program options");
            programOpts.updateOptions(params);
            initializeLogger();
            initializePasswords();
            if (!programOpts.isTerse() &&
                    !(params.size() == 1 && params.get("help") != null)) {
                // warn about deprecated use of program options
                // (except --help)
                // XXX - a lot of work for a nice message...
                Collection<ParamModel> programOptions =
                        ProgramOptions.getValidOptions();
                StringBuilder sb = new StringBuilder();
                sb.append(programOpts.getCommandName());
                for (Map.Entry<String,List<String>> p : params.entrySet()) {
                    // find the corresponding ParamModel
                    ParamModel opt = null;
                    for (ParamModel vo : programOptions) {
                        if (vo.getName().equalsIgnoreCase(p.getKey())) {
                            opt = vo;
                            break;
                        }
                    }
                    if (opt == null) {
                        continue;
                    }

                    // format the option appropriately
                    sb.append(" --").append(p.getKey());
                    List<String> pl = p.getValue();
                    // XXX - won't handle multi-values
                    if (opt.getType() == Boolean.class ||
                        opt.getType() == boolean.class) {
                        if (!pl.get(0).equalsIgnoreCase("true")) {
                            sb.append("=false");
                        }
                    } else if (pl != null && pl.size() > 0) {
                        sb.append(" ").append(pl.get(0));
                    }
                }
                sb.append(" ").append(name).append(" [options] ...");
                logger.info(strings.get("DeprecatedSyntax"));
                logger.info(sb.toString());
            }
        }
    }

    /**
     * Initialize the state of the logger based on any program options.
     */
    protected void initializeLogger() {
        if (!logger.isLoggable(Level.FINER)) {
            if (programOpts.isTerse())
                logger.setLevel(Level.INFO);
            else
                logger.setLevel(Level.FINE);
        }
    }

    /**
     * Initialize the passwords field based on the password
     * file specified in the program options, and initialize the
     * program option's password if available in the password file.
     */
    protected void initializePasswords() throws CommandException {
        passwords = new HashMap<String, String>();
        String pwfile = programOpts.getPasswordFile();

        if (ok(pwfile)) {
            passwords = CLIUtil.readPasswordFileOptions(pwfile, true);
            logger.finer("Passwords were read from password file: " +
                                        pwfile);
            String password = passwords.get(
                    Environment.getPrefix() + "PASSWORD");
            if (password != null && programOpts.getPassword() == null)
                programOpts.setPassword(password,
                    ProgramOptions.PasswordLocation.PASSWORD_FILE);
        }
    }

    /**
     * The prepare method must ensure that the commandModel field is set.
     */
    protected void prepare() throws CommandException {
        commandModel = new CommandModelImpl(this.getClass());
    }

    /**
     * The parse method sets the options and operands fields
     * based on the content of the command line arguments.
     * If the program options say this is a help request,
     * we set options and operands as if "--help" had been specified.
     *
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    protected void parse() throws CommandException {
        /*
         * If this is a help request, we don't need the command
         * metadata and we throw away all the other options and
         * fake everything else.
         */
        if (programOpts.isHelp()) {
            options = new ParameterMap();
            options.set("help", "true");
            operands = Collections.emptyList();
        } else {
            Parser rcp =
                new Parser(argv, 1, commandModel.getParameters(),
                    commandModel.unknownOptionsAreOperands());
            options = rcp.getOptions();
            operands = rcp.getOperands();

            /*
             * In the case where we're accepting unknown options as
             * operands, the special "--" delimiter will also be
             * accepted as an operand.  We eliminate it here.
             */
            if (commandModel.unknownOptionsAreOperands() &&
                    operands.size() > 0 && operands.get(0).equals("--"))
                operands.remove(0);
        }
        logger.finer("params: " + options);
        logger.finer("operands: " + operands);
    }

    /**
     * Check if the current request is a help request, either because
     * --help was specified as a program option or a command option.
     * If so, get the man page using the getManPage method, copy the
     * content to System.out, and return true.  Otherwise return false.
     * Subclasses may override this method to perform a different check
     * or to use a different method to display the man page.
     * If this method returns true, the validate and executeCommand methods
     * won't be called.
     */
    protected boolean checkHelp() throws CommandException {
        if (programOpts.isHelp()) {
            BufferedReader br = getManPage();
            if (br == null) {
                throw new CommandException(strings.get("ManpageMissing", name));
            }
            br = expandManPage(br);
            String line;
            try {
                while ((line = br.readLine()) != null)
                    System.out.println(line);
            } catch (IOException ioex) {
                throw new CommandException(
                            strings.get("ManpageMissing", name), ioex);
            } finally {
                try {
                    br.close();
                } catch (IOException ex) {
                }
            }
            return true;
        } else
            return false;
    }
   
    private Class<? extends Annotation> getScope(Class<?> onMe) {
      if (onMe == null) return null;
     
      for (Annotation anno : onMe.getAnnotations()) {
        if (anno.annotationType().isAnnotationPresent(Scope.class)) {
          return anno.annotationType();
        }
      }
     
      return null;
    }

    /**
     * The prevalidate method supplies missing options from
     * the environment.  It also supplies passwords from the password
     * file or prompts for them if interactive.
     *
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    protected void prevalidate() throws CommandException {
        /*
         * First, check that the command has the proper scope.
         * (Could check this in getCommand(), but at that point we
         * don't have the CommandModel yet.)
         * Remote commands are checked on the server.
         */
        if (!(this instanceof RemoteCommand) && !(this instanceof RemoteCLICommand)) {
          Class<? extends Annotation> myScope = getScope(this.getClass());
            if (myScope == null) {
                throw new CommandException(strings.get("NoScope", name));
            } else if (Singleton.class.equals(myScope)) {
                // check that there are no parameters for this command
                if (commandModel.getParameters().size() > 0) {
                    throw new CommandException(strings.get("HasParams", name));
                }
            }
        }

        /*
         * Check for missing options and operands.
         */
        Console cons = programOpts.isInteractive() ? System.console() : null;

        boolean missingOption = false;
        for (ParamModel opt : commandModel.getParameters()) {
            if (opt.getParam().password())
                continue;       // passwords are handled later
      if (opt.getParam().obsolete() && getOption(opt.getName()) != null)
    logger.info(
      strings.get("ObsoleteOption", opt.getName()));
            if (opt.getParam().optional())
                continue;
            if (opt.getParam().primary())
                continue;
            // if option isn't set, prompt for it (if interactive)
            if (getOption(opt.getName()) == null && cons != null &&
                    !missingOption) {
                cons.printf("%s",
                    strings.get("optionPrompt", lc(opt.getName())));
                String val = cons.readLine();
                if (ok(val))
                    options.set(opt.getName(), val);
            }
            // if it's still not set, that's an error
            if (getOption(opt.getName()) == null) {
                missingOption = true;
                logger.info(
                        strings.get("missingOption", "--" + opt.getName()));
            }
      if (opt.getParam().obsolete())  // a required obsolete option?
    logger.info(
      strings.get("ObsoleteOption", opt.getName()));
        }
        if (missingOption)
            throw new CommandValidationException(
                    strings.get("missingOptions", name));

        int operandMin = 0;
        int operandMax = 0;
        ParamModel operandParam = getOperandModel();
        if (operandParam != null) {
            operandMin = operandParam.getParam().optional() ? 0 : 1;
            operandMax = operandParam.getParam().multiple() ?
                                                        Integer.MAX_VALUE : 1;
        }

        if (operands.size() < operandMin && cons != null) {
            cons.printf("%s",
                strings.get("operandPrompt", operandParam.getName()));
            String val = cons.readLine();
            if (ok(val)) {
                operands = new ArrayList<String>();
                operands.add(val);
            }
        }
        if (operands.size() < operandMin)
            throw new CommandValidationException(
                    strings.get("notEnoughOperands", name,
                                operandParam.getType()));
        if (operands.size() > operandMax) {
            if (operandMax == 0)
                throw new CommandValidationException(
                    strings.get("noOperandsAllowed", name));
            else if (operandMax == 1)
                throw new CommandValidationException(
                    strings.get("tooManyOperands1", name));
            else
                throw new CommandValidationException(
                    strings.get("tooManyOperands", name, operandMax));
        }

        initializeCommandPassword();
    }

    /**
     * Inject this instance with the final values of all the command
     * parameters.
     *
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    protected void inject() throws CommandException {
        // injector expects operands to be in the ParameterMap with the key
        // "DEFAULT"
        options.set("DEFAULT", operands);

        // if command has a "terse" option, set it from ProgramOptions
        if (commandModel.getModelFor("terse") != null)
            options.set("terse", Boolean.toString(programOpts.isTerse()));

        // initialize the injector.
        InjectionResolver<Param> injector =
                    new MapInjectionResolver(commandModel, options);

        // inject
        try {
            injectionMgr.inject(this, injector);
        } catch (UnsatisfiedDependencyException e) {
            throw new CommandValidationException(e.getMessage(), e);
        }
    }

    /**
     * The validate method can be used by a subclass to validate
     * that the type and quantity of parameters and operands matches
     * the requirements for this command.
     *
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    protected void validate() throws CommandException {
    }

    /**
     * Execute the command using the options in options and the
     * operands in operands.
     *
     * @return the exit code
     * @throws CommandException if execution of the command fails
     * @throws CommandValidationException if there's something wrong
     *          with the options or arguments
     */
    protected abstract int executeCommand() throws CommandException;

    /**
     * Initialize all the passwords required by the command.
     *
     * @throws CommandException
     */
    private void initializeCommandPassword() throws CommandException {
        /*
         * Go through all the valid options and check for required password
         * options that weren't specified in the password file.  If option
         * is missing and we're interactive, prompt for it.  Store the
         * password as if it was a parameter.
         */
        for (ParamModel opt : commandModel.getParameters()) {
            if (!opt.getParam().password())
                continue;
            String pwdname = opt.getName();
            String pwd = getPassword(opt, null, true);
            if (pwd == null) {
                if (opt.getParam().optional())
                    continue;       // not required, skip it
                // if not terse, provide more advice about what to do
                String msg;
                if (programOpts.isTerse())
                    msg = strings.get("missingPassword", name, passwordName(opt));
                else
                    msg = strings.get("missingPasswordAdvice", name, passwordName(opt));
                throw new CommandValidationException(msg);
            }
            options.set(pwdname, pwd);
        }
    }

    protected String getPassword(String paramname, String localizedPrompt,
            String localizedPromptConfirm, boolean create) throws CommandValidationException {
        ParamModelData po = new ParamModelData(paramname, String.class, false, null);
        po.prompt = localizedPrompt;
        po.promptAgain = localizedPromptConfirm;
        po.param._password = true;
        return getPassword(po, null, create);
    }
   
    /**
     * Get a password for the given option.
     * First, look in the passwords map.  If found, return it.
     * If not found, and not required, return null;
     * If not interactive, return null.  Otherwise, prompt for the
     * password.  If create is true, prompt twice and compare the two values
     * to make sure they're the same.  If the password meets other validity
     * criteria (i.e., length) returns the password.  If defaultPassword is
     * not null, "Enter" selects this default password, which is returned.
     */
    protected String getPassword(ParamModel opt, String defaultPassword,
            boolean create) throws CommandValidationException {

        String passwordName = passwordName(opt);
        String password = passwords.get(passwordName);
        if (password != null)
            return password;

        if (opt.getParam().optional())
            return null;        // not required

        if (!programOpts.isInteractive())
            return null;        // can't prompt for it

        String prompt = null;
        String promptAgain = null;
        if (opt instanceof ParamModelData) {
            prompt = ((ParamModelData)opt).getPrompt();
            promptAgain = ((ParamModelData)opt).getPromptAgain();
        }
        String newprompt;
        if (ok(prompt)) {
            if (defaultPassword != null) {
                if (defaultPassword.length() == 0)
                    newprompt =
                        strings.get("NewPasswordDescriptionDefaultEmptyPrompt",
                                            prompt);
                else
                    newprompt =
                        strings.get("NewPasswordDescriptionDefaultPrompt",
                                            prompt, defaultPassword);
            } else
                newprompt =
                    strings.get("NewPasswordDescriptionPrompt", prompt);
        } else {
            if (defaultPassword != null) {
                if (defaultPassword.length() == 0)
                    newprompt =
                        strings.get("NewPasswordDefaultEmptyPrompt",
                                            passwordName);
                else
                    newprompt =
                        strings.get("NewPasswordDefaultPrompt",
                                            passwordName, defaultPassword);
            } else
                newprompt = strings.get("NewPasswordPrompt", passwordName);
        }

        String newpassword = readPassword(newprompt);

        /*
         * If we allow for a default password, and the user just hit "Enter",
         * return the default password.  No need to prompt twice or check
         * for validity.
         */
        if (defaultPassword != null) {
            if (newpassword == null)
                newpassword = "";
            if (newpassword.length() == 0) {
                newpassword = defaultPassword;
                passwords.put(passwordName, newpassword);
                return newpassword;
            }
        }

        /*
         * If not creating a new password, don't need to verify that
         * the user typed it correctly by making them type it twice,
         * and don't need to check it for validity.  Just return what
         * we have.
         */
        if (!create) {
            passwords.put(passwordName, newpassword);
            return newpassword;
        }

        String confirmationPrompt;
        if (ok(promptAgain)) {
            confirmationPrompt =
                strings.get("NewPasswordDescriptionPrompt", promptAgain);
        } else {
            confirmationPrompt =
                strings.get("NewPasswordConfirmationPrompt", passwordName);
        }
        String newpasswordAgain = readPassword(confirmationPrompt);
        if (!newpassword.equals(newpasswordAgain)) {
            throw new CommandValidationException(
                strings.get("OptionsDoNotMatch",
                            ok(prompt) ? prompt : passwordName));
        }
        passwords.put(passwordName, newpassword);
        return newpassword;
    }

    private String passwordName(ParamModel opt) {
        return Environment.getPrefix() + opt.getName().toUpperCase(Locale.ENGLISH);
    }

    /**
     * Display the given prompt and read a password without echoing it.
     * Returns null if no console available.
     */
    protected String readPassword(String prompt) {
        String password = null;
        Console cons = System.console();
        if (cons != null) {
            char[] pc = cons.readPassword("%s", prompt);
            // yes, yes, yes, it would be safer to not keep it in a String
            password = new String(pc);
        }
        return password;
    }

    /**
     * Get the ParamModel that corresponds to the operand
     * (primary parameter).  Return null if none.
     */
    protected ParamModel getOperandModel() {
        for (ParamModel pm : commandModel.getParameters()) {
            if (pm.getParam().primary())
                return pm;
        }
        return null;
    }

    /**
     * Get an option value, that might come from the command line
     * or from the environment.  Return the default value for the
     * option if not otherwise specified.
     */
    protected String getOption(String name) {
        String val = options.getOne(name);
        if (val == null) {
            val = env.getStringOption(name);
        }
        if (val == null) {
            // no value, find the default
            ParamModel opt = commandModel.getModelFor(name);
            // if no value was specified and there's a default value, return it
            if (opt != null) {
                String def = opt.getParam().defaultValue();
                if (ok(def)) {
                    val = def;
                }
            }
        }
        return val;
    }

    /**
     * Get option values, that might come from the command line
     * or from the environment.  Return the default value for the
     * option if not otherwise specified. This method works with options
     * for with multiple() is true.
     */
    protected List<String> getOptions(String name) {
        List<String> val = options.get(name);
        if (val.isEmpty()) {
            String v = env.getStringOption(name);
            if (v != null) {
                val.add(v);
            }
        }
        if (val.isEmpty()) {
            // no value, find the default
            ParamModel opt = commandModel.getModelFor(name);
            // if no value was specified and there's a default value, return it
            if (opt != null) {
                String def = opt.getParam().defaultValue();
                if (ok(def)) {
                    val.add(def);
                }
            }
        }
        return val;
    }
    /**
     * Get a boolean option value, that might come from the command line
     * or from the environment.
     */
    protected boolean getBooleanOption(String name) {
        String val = getOption(name);
        return val != null && Boolean.parseBoolean(val);
    }

    /**
     * Return the named system property, or property
     * set in asenv.conf.
     */
    protected String getSystemProperty(String name) {
        return systemProps.get(name);
    }

    /**
     * Return all the system properties and properties set
     * in asenv.conf.  The returned Map may not be modified.
     */
    protected Map<String,String> getSystemProperties() {
        return systemProps;
    }

    /**
     * If this is an unsupported command, throw an exception.
     */
    private static void checkUnsupportedLegacyCommand(String cmd)
            throws CommandException {
        for (String c : unsupported) {
            if (c.equals(cmd)) {
                throw new CommandException(
                            strings.get("UnsupportedLegacyCommand", cmd));
            }
        }
        // it is a supported command; do nothing
    }

    /**
     * Prints the exception message with level as FINER.
     *
     * @param e the exception object to print
     */
    protected void printExceptionStackTrace(java.lang.Throwable e) {
        if (!logger.isLoggable(Level.FINER))
            return;
  /*
  java.lang.StackTraceElement[] ste = e.getStackTrace();
  for (int ii = 0; ii < ste.length; ii++)
      printDebugMessage(ste[ii].toString());
  */
      final ByteArrayOutputStream output = new ByteArrayOutputStream(512);
      e.printStackTrace(new java.io.PrintStream(output));
        try {
            output.close();
        } catch (IOException ex) {
            // ignore
        }
        logger.finer(output.toString());
    }

    protected static boolean ok(String s) {
        return s != null && s.length() > 0;
    }

    // shorthand for this too-verbose operation
    private static String lc(String s) {
      return s.toLowerCase(Locale.ENGLISH);
    }

    /**
     * Read the named resource file and add the first token on each line
     * to the set.  Skip comment lines.
     */
    private static void file2Set(String file, Set<String> set) {
        BufferedReader reader = null;
        try {
            InputStream is = CLICommand.class.getClassLoader().
                                getResourceAsStream(file);
            if (is == null)
                return;     // in case the resource doesn't exist
            reader = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#"))
                    continue; // # indicates comment
                StringTokenizer tok = new StringTokenizer(line, " ");
                // handles with or without space, rudimendary as of now
                String cmd = tok.nextToken();
                set.add(cmd);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ee) {
                    // ignore
                }

            }
        }
    }
}
TOP

Related Classes of com.sun.enterprise.admin.cli.CLICommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.