001    /*
002     * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003     *
004     * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
005     *
006     * The contents of this file are subject to the terms of either the GNU
007     * General Public License Version 2 only ("GPL") or the Common Development
008     * and Distribution License("CDDL") (collectively, the "License").  You
009     * may not use this file except in compliance with the License.  You can
010     * obtain a copy of the License at
011     * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012     * or packager/legal/LICENSE.txt.  See the License for the specific
013     * language governing permissions and limitations under the License.
014     *
015     * When distributing the software, include this License Header Notice in each
016     * file and include the License file at packager/legal/LICENSE.txt.
017     *
018     * GPL Classpath Exception:
019     * Oracle designates this particular file as subject to the "Classpath"
020     * exception as provided by Oracle in the GPL Version 2 section of the License
021     * file that accompanied this code.
022     *
023     * Modifications:
024     * If applicable, add the following below the License Header, with the fields
025     * enclosed by brackets [] replaced by your own identifying information:
026     * "Portions Copyright [year] [name of copyright owner]"
027     *
028     * Contributor(s):
029     * If you wish your version of this file to be governed by only the CDDL or
030     * only the GPL Version 2, indicate your decision by adding "[Contributor]
031     * elects to include this software in this distribution under the [CDDL or GPL
032     * Version 2] license."  If you don't indicate a single choice of license, a
033     * recipient has the option to distribute your version of this file under
034     * either the CDDL, the GPL Version 2 or to extend the choice of license to
035     * its licensees as provided above.  However, if you add GPL Version 2 code
036     * and therefore, elected the GPL Version 2 license, then the option applies
037     * only if the new code is made subject to such option by the copyright
038     * holder.
039     */
040    
041    package com.sun.enterprise.admin.cli;
042    
043    import java.io.*;
044    import java.util.*;
045    import java.lang.reflect.*;
046    import java.util.logging.*;
047    
048    import org.jvnet.hk2.annotations.*;
049    import org.jvnet.hk2.component.*;
050    import com.sun.hk2.component.InjectionResolver;
051    
052    import org.glassfish.api.Param;
053    import org.glassfish.api.admin.*;
054    import org.glassfish.api.admin.CommandModel.ParamModel;
055    import org.glassfish.common.util.admin.CommandModelImpl;
056    import org.glassfish.common.util.admin.MapInjectionResolver;
057    import org.glassfish.common.util.admin.ManPageFinder;
058    
059    import com.sun.enterprise.admin.util.CommandModelData.ParamModelData;
060    import com.sun.enterprise.admin.cli.remote.RemoteCommand;
061    import com.sun.enterprise.universal.i18n.LocalStringsImpl;
062    import com.sun.enterprise.universal.glassfish.ASenvPropertyReader;
063    import com.sun.logging.LogDomains;
064    
065    
066    /**
067     * Base class for a CLI command.  An instance of a subclass of this
068     * class is created using the getCommand method with the name of the
069     * command and the information about its environment.
070     * <p>
071     * A command is executed with a list of arguments using the execute
072     * method.  The implementation of the execute method in this class
073     * saves the arguments in the protected argv field, then calls the
074     * following protected methods in order: prepare, parse, validate,
075     * and executeCommand.  A subclass must implement the prepare method
076     * to initialize the metadata that specified the valid options for
077     * the command, and the executeCommand method to actually perform the
078     * command.  The parse and validate method may also be overridden if
079     * needed.  Or, the subclass may override the execute method and
080     * provide the complete implementation for the command, including
081     * option parsing.
082     *
083     * @author Bill Shannon
084     */
085    @Contract
086    @Scoped(PerLookup.class)
087    public abstract class CLICommand implements PostConstruct {
088        public static final int ERROR = CLIConstants.ERROR;
089        public static final int CONNECTION_ERROR = 2;
090        public static final int INVALID_COMMAND_ERROR = 3;
091        public static final int SUCCESS = CLIConstants.SUCCESS;
092        public static final int WARNING = CLIConstants.WARNING;
093    
094        private static final Set<String> unsupported;
095        private static final String UNSUPPORTED_CMD_FILE_NAME =
096                                        "unsupported-legacy-command-names";
097        private static final String PACKAGE_NAME = "com.sun.enterprise.admin.cli";
098    
099        private static final LocalStringsImpl strings =
100                new LocalStringsImpl(CLICommand.class);
101    
102        private static final Map<String,String> systemProps = 
103                Collections.unmodifiableMap(new ASenvPropertyReader().getProps());
104    
105        protected static final Logger logger =
106                Logger.getLogger(CLICommand.class.getPackage().getName());
107    
108        // InjectionManager is completely stateless with only one method that
109        // operates on its arguments, so we can share a single instance.
110        private static final InjectionManager injectionMgr = new InjectionManager();
111    
112        /**
113         * The name of the command.
114         * Initialized in the constructor.
115         */
116        protected String name;
117    
118        /**
119         * The program options for the command.
120         * Initialized in the constructor.
121         */
122        @Inject
123        protected ProgramOptions programOpts;
124    
125        /**
126         * The environment for the command.
127         * Initialized in the constructor.
128         */
129        @Inject
130        protected Environment env;
131    
132        /**
133         * The command line arguments for this execution.
134         * Initialized in the execute method.
135         */
136        protected String[] argv;
137    
138        /**
139         * The metadata describing the command's options and operands.
140         */
141        protected CommandModel commandModel;
142    
143        protected StringBuilder metadataErrors;
144    
145        /**
146         * The options parsed from the command line.
147         * Initialized by the parse method.  The keys
148         * are the parameter names from the command model,
149         * not the "forced to all lower case" names that
150         * are presented to the user.
151         */
152        protected ParameterMap options;
153    
154        /**
155         * The operands parsed from the command line.
156         * Initialized by the parse method.
157         */
158        protected List<String> operands;
159    
160        /**
161         * The passwords read from the password file.
162         * Initialized by the initializeCommandPassword method.
163         */
164        protected Map<String, String> passwords;
165    
166        static {
167            Set<String> unsup = new HashSet<String>();
168            file2Set(UNSUPPORTED_CMD_FILE_NAME, unsup);
169            unsupported = Collections.unmodifiableSet(unsup);
170        }
171    
172        /**
173         * Get a CLICommand object representing the named command.
174         */
175        public static CLICommand getCommand(Habitat habitat, String name)
176                throws CommandException {
177    
178            // first, check if it's a known unsupported command
179            checkUnsupportedLegacyCommand(name);
180    
181            // next, try to load our own implementation of the command
182            CLICommand cmd = habitat.getComponent(CLICommand.class, name);
183            if (cmd != null)
184                return cmd;
185    
186            // nope, must be a remote command
187            logger.finer("Assuming it's a remote command: " + name);
188            return new RemoteCommand(name,
189                habitat.getComponent(ProgramOptions.class),
190                habitat.getComponent(Environment.class));
191        }
192    
193        /**
194         * Constructor used by subclasses when instantiated by HK2.
195         * ProgramOptions and Environment are injected.  name is set here.
196         */
197        protected CLICommand() {
198            Service service = this.getClass().getAnnotation(Service.class);
199    
200            if (service == null)
201                name = "unknown-command";   // should never happen
202            else
203                name = service.name();
204        }
205    
206        /**
207         * Initialize the logger after being instantiated by HK2.
208         */
209        public void postConstruct() {
210            initializeLogger();
211        }
212    
213        /**
214         * Constructor used by subclasses to save the name, program options,
215         * and environment information into corresponding protected fields.
216         * Finally, this constructor calls the initializeLogger method.
217         */
218        protected CLICommand(String name, ProgramOptions programOpts,
219                Environment env) {
220            this.name = name;
221            this.programOpts = programOpts;
222            this.env = env;
223            initializeLogger();
224        }
225    
226        /**
227         * Execute this command with the given arguemnts.
228         * The implementation in this class saves the passed arguments in
229         * the argv field and calls the initializePasswords method.
230         * Then it calls the prepare, parse, and validate methods, finally
231         * returning the result of calling the executeCommand method.
232         * Note that argv[0] is the command name.
233         *
234         * @throws CommandException if execution of the command fails
235         * @throws CommandValidationException if there's something wrong
236         *          with the options or arguments
237         */
238        public int execute(String... argv) throws CommandException {
239            this.argv = argv;
240            initializePasswords();
241            logger.finer("Prepare");
242            prepare();
243            logger.finer("Process program options");
244            processProgramOptions();
245            logger.finer("Parse command options");
246            parse();
247            if (checkHelp())
248                return 0;
249            logger.finer("Prevalidate command options");
250            prevalidate();
251            logger.finer("Inject command options");
252            inject();
253            logger.finer("Validate command options");
254            validate();
255            if (programOpts.isEcho()) {
256                logger.info(echoCommand());
257                // In order to avoid echoing commands used intenally to the
258                // implementation of *this* command, we turn off echo after
259                // having echoed this command.
260                programOpts.setEcho(false);
261            } else if (logger.isLoggable(Level.FINER))
262                logger.finer(echoCommand());
263            logger.finer("Execute command");
264            return executeCommand();
265        }
266    
267        /**
268         * Return the name of this command.
269         */
270        public String getName() {
271            return name;
272        }
273    
274        /**
275         * Returns the program options associated with this command.
276         * 
277         * @return the command's program options
278         */
279        public ProgramOptions getProgramOptions() {
280            return programOpts;
281        }
282    
283        /**
284         * Return a BufferedReader for the man page for this command,
285         * or null if not found.
286         */
287        public BufferedReader getManPage() {
288            String commandName = getName();
289            if (commandName.length() == 0)
290                throw new IllegalArgumentException("Command name cannot be empty");
291    
292            // special case "help" --> "asadmin"
293            if (commandName.equals("help"))
294                commandName = "asadmin";
295    
296            return ManPageFinder.getCommandManPage(
297                                    commandName,
298                                    getClass().getName(),
299                                    Locale.getDefault(),
300                                    getClass().getClassLoader(),
301                                    logger);
302        }
303    
304        /**
305         * Get the usage text.
306         *
307         * @return usage text
308         */
309        public String getUsage() {
310            String usage;
311            if (commandModel != null && ok(usage = commandModel.getUsageText())) {
312                StringBuffer usageText = new StringBuffer();
313                usageText.append(
314                    strings.get("Usage", strings.get("Usage.asadmin")));
315                usageText.append(" ");
316                usageText.append(usage);
317                return usageText.toString();
318            } else {
319                return generateUsageText();
320            }
321        }
322    
323        private String generateUsageText() {
324            StringBuilder usageText = new StringBuilder();
325            usageText.append(strings.get("Usage", strings.get("Usage.asadmin")));
326            usageText.append(" ");
327            usageText.append(getName());
328            int len = usageText.length();
329            StringBuilder optText = new StringBuilder();
330            String lsep = System.getProperty("line.separator");
331            for (ParamModel opt : usageOptions()) {
332                optText.setLength(0);
333                final String optName = lc(opt.getName());
334                // "--terse" is part of asadmin utility options
335                if (optName.equals("terse"))
336                    continue;
337                // skip "hidden" options
338                if (optName.startsWith("_"))
339                    continue;
340                // do not want to display password as an option
341                if (opt.getParam().password())
342                    continue;
343                // also do not want to display obsolete options
344                if (opt.getParam().obsolete())
345                    continue;
346                // primary parameter is the operand, not an option
347                if (opt.getParam().primary())
348                    continue;
349                boolean optional = opt.getParam().optional();
350                String defValue = opt.getParam().defaultValue();
351                if (optional)
352                    optText.append("[");
353                String sn = opt.getParam().shortName();
354                if (ok(sn))
355                    optText.append('-').append(sn).append('|');
356                optText.append("--").append(optName);
357    
358                if (opt.getType() == Boolean.class ||
359                    opt.getType() == boolean.class) {
360                    // canonicalize default value
361                    if (ok(defValue) && Boolean.parseBoolean(defValue))
362                        defValue = "true";
363                    else
364                        defValue = "false";
365                    optText.append("[=<").append(optName);
366                    optText.append(strings.get("Usage.default", defValue));
367                    optText.append(">]");
368                } else {    // STRING or FILE
369                    if (ok(defValue)) {
370                        optText.append(" <").append(optName);
371                        optText.append(strings.get("Usage.default", defValue));
372                        optText.append('>');
373                    } else
374                        optText.append(" <").append(optName).append('>');
375                }
376                if (optional)
377                    optText.append("]");
378    
379                if (len + 1 + optText.length() > 80) {
380                    usageText.append(lsep).append('\t');
381                    len = 8;
382                } else {
383                    usageText.append(' ');
384                    len++;
385                }
386                usageText.append(optText);
387                len += optText.length();
388            }
389    
390            // add --help text
391            String helpText = "[-?|--help[=<help(default:false)>]]";
392            if (len + 1 + helpText.length() > 80) {
393                usageText.append(lsep).append('\t');
394                len = 8;
395            } else {
396                usageText.append(' ');
397                len++;
398            }
399            usageText.append(helpText);
400            len += helpText.length();
401    
402            optText.setLength(0);
403            ParamModel operandParam = getOperandModel();
404            String opname = operandParam != null ?
405                    lc(operandParam.getName()) : null;
406            if (!ok(opname))
407                opname = "operand";
408    
409            int operandMin = 0;
410            int operandMax = 0;
411            if (operandParam != null) {
412                operandMin = operandParam.getParam().optional() ? 0 : 1;
413                operandMax = operandParam.getParam().multiple() ?
414                                                            Integer.MAX_VALUE : 1;
415            }
416            if (operandMax > 0) {
417                if (operandMin == 0) {
418                    optText.append("[").append(opname);
419                    if (operandMax > 1)
420                        optText.append(" ...");
421                    optText.append("]");
422                } else {
423                    optText.append(opname);
424                    if (operandMax > 1)
425                        optText.append(" ...");
426                }
427            }
428            if (len + 1 + optText.length() > 80) {
429                usageText.append(lsep).append('\t');
430                len = 8;
431            } else {
432                usageText.append(' ');
433                len++;
434            }
435            usageText.append(optText);
436            return usageText.toString();
437        }
438    
439        /**
440         * Subclasses can override this method to supply additional
441         * or different options that should be part of the usage text.
442         * Most commands will never need to do this, but the create-domain
443         * command uses it to include the --user option as a required option.
444         */
445        protected Collection<ParamModel> usageOptions() {
446            return commandModel.getParameters();
447        }
448    
449        @Override
450        public String toString() {
451            return echoCommand();
452        }
453    
454    
455        /**
456         * Return a string representing the command line used with this command.
457         */
458        private String echoCommand() {
459            StringBuilder sb = new StringBuilder();
460    
461            // first, the program options
462            sb.append("asadmin ");
463            sb.append(programOpts.toString()).append(' ');
464    
465            // now the subcommand options and operands
466            sb.append(name).append(' ');
467    
468            // have we parsed any options yet?
469            if (options != null && operands != null) {
470                for (ParamModel opt : commandModel.getParameters()) {
471                    if (opt.getParam().password())
472                        continue;       // don't print passwords
473                    if (opt.getParam().primary())
474                        continue;
475                    // include every option that was specified on the command line
476                    // and every option that has a default value
477                    String value = getOption(opt.getName());
478                    if (value == null) {
479                        value = opt.getParam().defaultValue();
480                        if (value != null && value.length() == 0)
481                            value = null;
482                    }
483                    if (value != null) {
484                        sb.append("--").append(lc(opt.getName()));
485                        if (opt.getType() == Boolean.class ||
486                            opt.getType() == boolean.class) {
487                            if (Boolean.parseBoolean(value))
488                                sb.append("=").append("true");
489                            else
490                                sb.append("=").append("false");
491                        } else {    // STRING or FILE
492                            sb.append(" ").append(quote(value));
493                        }
494                        sb.append(' ');
495                    }
496                }
497                for (String o : operands)
498                    sb.append(quote(o)).append(' ');
499            } else if (argv != null) {
500                // haven't parsed any options, include raw arguments, if any
501                for (String arg : argv)
502                    sb.append(quote(arg)).append(' ');
503            }
504    
505            sb.setLength(sb.length() - 1);  // strip trailing space
506            return sb.toString();
507        }
508    
509        /**
510         * Quote a value, if the value contains any special characters.
511         *
512         * @param   value   value to be quoted
513         * @return          the possibly quoted value
514         */
515        public static String quote(String value) {
516            int len = value.length();
517            if (len == 0)
518                return "\"\"";      // an empty string is handled specially
519    
520            /*
521             * Look for any special characters.  Escape and
522             * quote the entire string if necessary.
523             */
524            boolean needQuoting = false;
525            for (int i = 0; i < len; i++) {
526                char c = value.charAt(i);
527                if (c == '"' || c == '\\' || c == '\r' || c == '\n') {
528                    // need to escape them and then quote the whole string
529                    StringBuffer sb = new StringBuffer(len + 3);
530                    sb.append('"');
531                    sb.append(value.substring(0, i));
532                    int lastc = 0;
533                    for (int j = i; j < len; j++) {
534                        char cc = value.charAt(j);
535                        if ((cc == '"') || (cc == '\\') || 
536                            (cc == '\r') || (cc == '\n'))
537                            if (cc == '\n' && lastc == '\r')
538                                ;   // do nothing, CR was already escaped
539                            else
540                                sb.append('\\');    // Escape the character
541                        sb.append(cc);
542                        lastc = cc;
543                    }
544                    sb.append('"');
545                    return sb.toString();
546                } else if (c <= 040 || c >= 0177)
547                    // These characters cause the string to be quoted
548                    needQuoting = true;
549            }
550    
551            if (needQuoting) {
552                StringBuffer sb = new StringBuffer(len + 2);
553                sb.append('"').append(value).append('"');
554                return sb.toString();
555            } else 
556                return value;
557        }
558    
559        /**
560         * If the program options haven't already been set, parse them
561         * on the command line and remove them from the command line.
562         * Subclasses should call this method in their prepare method
563         * after initializing commandOpts (so usage is available on failure)
564         * if they want to allow program options after the command name.
565         * Currently RemoteCommand does this, as well as the local commands
566         * that also need to talk to the server.
567         */
568        protected void processProgramOptions() throws CommandException {
569            if (!programOpts.isOptionsSet()) {
570                logger.finer("Parsing program options");
571                /*
572                 * asadmin options and command options are intermixed.
573                 * Parse the entire command line for asadmin options,
574                 * removing them from the command line, and ignoring
575                 * unknown options.
576                 */
577                Parser rcp = new Parser(argv, 0,
578                                ProgramOptions.getValidOptions(), true);
579                ParameterMap params = rcp.getOptions();
580                List<String> operands = rcp.getOperands();
581                argv = operands.toArray(new String[operands.size()]);
582                if (params.size() > 0) {
583                    // at least one program option specified after command name
584                    logger.finer("Update program options");
585                    programOpts.updateOptions(params);
586                    initializeLogger();
587                    initializePasswords();
588                    if (!programOpts.isTerse() &&
589                            !(params.size() == 1 && params.get("help") != null)) {
590                        // warn about deprecated use of program options
591                        // (except --help)
592                        // XXX - a lot of work for a nice message...
593                        Collection<ParamModel> programOptions =
594                                ProgramOptions.getValidOptions();
595                        StringBuilder sb = new StringBuilder();
596                        sb.append("asadmin");
597                        for (Map.Entry<String,List<String>> p : params.entrySet()) {
598                            // find the corresponding ParamModel
599                            ParamModel opt = null;
600                            for (ParamModel vo : programOptions) {
601                                if (vo.getName().equalsIgnoreCase(p.getKey())) {
602                                    opt = vo;
603                                    break;
604                                }
605                            }
606                            if (opt == null)        // should never happen
607                                continue;
608    
609                            // format the option appropriately
610                            sb.append(" --").append(p.getKey());
611                            List<String> pl = p.getValue();
612                            // XXX - won't handle multi-values
613                            if (opt.getType() == Boolean.class ||
614                                opt.getType() == boolean.class) {
615                                if (!pl.get(0).equalsIgnoreCase("true"))
616                                    sb.append("=false");
617                            } else {
618                                if (pl != null && pl.size() > 0)
619                                    sb.append(" ").append(pl.get(0));
620                            }
621                        }
622                        sb.append(" ").append(name).append(" [options] ...");
623                        logger.info(strings.get("DeprecatedSyntax"));
624                        logger.info(sb.toString());
625                    }
626                }
627            }
628        }
629    
630        /**
631         * Initialize the state of the logger based on any program options.
632         */
633        protected void initializeLogger() {
634            if (!logger.isLoggable(Level.FINER)) {
635                if (programOpts.isTerse())
636                    logger.setLevel(Level.INFO);
637                else
638                    logger.setLevel(Level.FINE);
639            }
640        }
641    
642        /**
643         * Initialize the passwords field based on the password
644         * file specified in the program options, and initialize the
645         * program option's password if available in the password file.
646         */
647        protected void initializePasswords() throws CommandException {
648            passwords = new HashMap<String, String>();
649            String pwfile = programOpts.getPasswordFile();
650    
651            if (ok(pwfile)) {
652                passwords = CLIUtil.readPasswordFileOptions(pwfile, true);
653                logger.finer("Passwords were read from password file: " +
654                                            pwfile);
655                String password = passwords.get(
656                        Environment.AS_ADMIN_ENV_PREFIX + "PASSWORD");
657                if (password != null && programOpts.getPassword() == null)
658                    programOpts.setPassword(password,
659                        ProgramOptions.PasswordLocation.PASSWORD_FILE);
660            }
661        }
662    
663        /**
664         * The prepare method must ensure that the commandModel field is set.
665         */
666        protected void prepare() throws CommandException {
667            commandModel = new CommandModelImpl(this.getClass());
668        }
669    
670        /**
671         * The parse method sets the options and operands fields
672         * based on the content of the command line arguments.
673         * If the program options say this is a help request,
674         * we set options and operands as if "--help" had been specified.
675         *
676         * @throws CommandException if execution of the command fails
677         * @throws CommandValidationException if there's something wrong
678         *          with the options or arguments
679         */
680        protected void parse() throws CommandException {
681            /*
682             * If this is a help request, we don't need the command
683             * metadata and we throw away all the other options and
684             * fake everything else.
685             */
686            if (programOpts.isHelp()) {
687                options = new ParameterMap();
688                options.set("help", "true");
689                operands = Collections.emptyList();
690            } else {
691                Parser rcp =
692                    new Parser(argv, 1, commandModel.getParameters(),
693                        commandModel.unknownOptionsAreOperands());
694                options = rcp.getOptions();
695                operands = rcp.getOperands();
696    
697                /*
698                 * In the case where we're accepting unknown options as
699                 * operands, the special "--" delimiter will also be
700                 * accepted as an operand.  We eliminate it here.
701                 */
702                if (commandModel.unknownOptionsAreOperands() &&
703                        operands.size() > 0 && operands.get(0).equals("--"))
704                    operands.remove(0);
705            }
706            logger.finer("params: " + options);
707            logger.finer("operands: " + operands);
708        }
709    
710        /**
711         * Check if the current request is a help request, either because
712         * --help was specified as a programoption or a command option.
713         * If so, get the man page using the getManPage method, copy the
714         * content to System.out, and return true.  Otherwise return false.
715         * Subclasses may override this method to perform a different check
716         * or to use a different method to display the man page.
717         * If this method returns true, the validate and executeCommand methods
718         * won't be called.
719         */
720        protected boolean checkHelp() throws CommandException {
721            if (programOpts.isHelp()) {
722                BufferedReader br = getManPage();
723                if (br == null)
724                    throw new CommandException(strings.get("ManpageMissing", name));
725                String line;
726                try {
727                    while ((line = br.readLine()) != null)
728                        System.out.println(line);
729                } catch (IOException ioex) {
730                    throw new CommandException(
731                                strings.get("ManpageMissing", name), ioex);
732                } finally {
733                    try {
734                        br.close();
735                    } catch (IOException ex) {
736                    }
737                }
738                return true;
739            } else
740                return false;
741        }
742    
743        /**
744         * The prevalidate method supplies missing options from
745         * the environment.  It also supplies passwords from the password
746         * file or prompts for them if interactive.
747         *
748         * @throws CommandException if execution of the command fails
749         * @throws CommandValidationException if there's something wrong
750         *          with the options or arguments
751         */
752        protected void prevalidate() throws CommandException {
753            /*
754             * First, check that the command has the proper scope.
755             * (Could check this in getCommand(), but at that point we
756             * don't have the CommandModel yet.)
757             * Remote commands are checked on the server.
758             */
759            if (!(this instanceof RemoteCommand)) {
760                Scoped scoped = this.getClass().getAnnotation(Scoped.class);
761                if (scoped == null) {
762                    throw new CommandException(strings.get("NoScope", name));
763                } else if (scoped.value() == Singleton.class) {
764                    // check that there are no parameters for this command
765                    if (commandModel.getParameters().size() > 0) {
766                        throw new CommandException(strings.get("HasParams", name));
767                    }
768                }
769            }
770    
771            /*
772             * Check for missing options and operands.
773             */
774            Console cons = programOpts.isInteractive() ? System.console() : null;
775    
776            boolean missingOption = false;
777            for (ParamModel opt : commandModel.getParameters()) {
778                if (opt.getParam().password())
779                    continue;       // passwords are handled later
780                if (opt.getParam().obsolete() && getOption(opt.getName()) != null)
781                    logger.info(
782                            strings.get("ObsoleteOption", opt.getName()));
783                if (opt.getParam().optional())
784                    continue;
785                if (opt.getParam().primary())
786                    continue;
787                // if option isn't set, prompt for it (if interactive)
788                if (getOption(opt.getName()) == null && cons != null &&
789                        !missingOption) {
790                    cons.printf("%s",
791                        strings.get("optionPrompt", lc(opt.getName())));
792                    String val = cons.readLine();
793                    if (ok(val))
794                        options.set(opt.getName(), val);
795                }
796                // if it's still not set, that's an error
797                if (getOption(opt.getName()) == null) {
798                    missingOption = true;
799                    logger.info(
800                            strings.get("missingOption", "--" + opt.getName()));
801                }
802                if (opt.getParam().obsolete())      // a required obsolete option?
803                    logger.info(
804                            strings.get("ObsoleteOption", opt.getName()));
805            }
806            if (missingOption)
807                throw new CommandValidationException(
808                        strings.get("missingOptions", name));
809    
810            int operandMin = 0;
811            int operandMax = 0;
812            ParamModel operandParam = getOperandModel();
813            if (operandParam != null) {
814                operandMin = operandParam.getParam().optional() ? 0 : 1;
815                operandMax = operandParam.getParam().multiple() ?
816                                                            Integer.MAX_VALUE : 1;
817            }
818    
819            if (operands.size() < operandMin && cons != null) {
820                cons.printf("%s",
821                    strings.get("operandPrompt", operandParam.getName()));
822                String val = cons.readLine();
823                if (ok(val)) {
824                    operands = new ArrayList<String>();
825                    operands.add(val);
826                }
827            }
828            if (operands.size() < operandMin)
829                throw new CommandValidationException(
830                        strings.get("notEnoughOperands", name,
831                                    operandParam.getType()));
832            if (operands.size() > operandMax) {
833                if (operandMax == 0)
834                    throw new CommandValidationException(
835                        strings.get("noOperandsAllowed", name));
836                else if (operandMax == 1)
837                    throw new CommandValidationException(
838                        strings.get("tooManyOperands1", name));
839                else
840                    throw new CommandValidationException(
841                        strings.get("tooManyOperands", name, operandMax));
842            }
843    
844            initializeCommandPassword();
845        }
846    
847        /**
848         * Inject this instance with the final values of all the command
849         * parameters.
850         *
851         * @throws CommandException if execution of the command fails
852         * @throws CommandValidationException if there's something wrong
853         *          with the options or arguments
854         */
855        protected void inject() throws CommandException {
856            // injector expects operands to be in the ParameterMap with the key
857            // "DEFAULT"
858            options.set("DEFAULT", operands);
859    
860            // if command has a "terse" option, set it from ProgramOptions
861            if (commandModel.getModelFor("terse") != null)
862                options.set("terse", Boolean.toString(programOpts.isTerse()));
863    
864            // initialize the injector.
865            InjectionResolver<Param> injector =
866                        new MapInjectionResolver(commandModel, options);
867    
868            // inject
869            try {
870                injectionMgr.inject(this, injector);
871            } catch (UnsatisfiedDependencyException e) {
872                throw new CommandValidationException(e.getMessage(), e);
873            }
874        }
875    
876        /**
877         * The validate method can be used by a subclass to validate
878         * that the type and quantity of parameters and operands matches
879         * the requirements for this command.
880         *
881         * @throws CommandException if execution of the command fails
882         * @throws CommandValidationException if there's something wrong
883         *          with the options or arguments
884         */
885        protected void validate() throws CommandException {
886        }
887    
888        /**
889         * Execute the command using the options in options and the
890         * operands in operands.
891         *
892         * @return the exit code
893         * @throws CommandException if execution of the command fails
894         * @throws CommandValidationException if there's something wrong
895         *          with the options or arguments
896         */
897        protected abstract int executeCommand() throws CommandException;
898    
899        /**
900         * Initialize all the passwords required by the command.
901         *
902         * @throws CommandException
903         */
904        private void initializeCommandPassword() throws CommandException {
905            /*
906             * Go through all the valid options and check for required password
907             * options that weren't specified in the password file.  If option
908             * is missing and we're interactive, prompt for it.  Store the
909             * password as if it was a parameter.
910             */
911            for (ParamModel opt : commandModel.getParameters()) {
912                if (!opt.getParam().password())
913                    continue;
914                String pwdname = opt.getName();
915                String pwd = getPassword(opt, null, true);
916                if (pwd == null) {
917                    if (opt.getParam().optional())
918                        continue;       // not required, skip it
919                    // if not terse, provide more advice about what to do
920                    String msg;
921                    if (programOpts.isTerse())
922                        msg = strings.get("missingPassword", name, pwdname);
923                    else
924                        msg = strings.get("missingPasswordAdvice", name, pwdname);
925                    throw new CommandValidationException(msg);
926                }
927                options.set(pwdname, pwd);
928            }
929        }
930    
931        /**
932         * Get a password for the given option.
933         * First, look in the passwords map.  If found, return it.
934         * If not found, and not required, return null;
935         * If not interactive, return null.  Otherwise, prompt for the
936         * password.  If create is true, prompt twice and compare the two values
937         * to make sure they're the same.  If the password meets other validity
938         * criteria (i.e., length) returns the password.  If defaultPassword is
939         * not null, "Enter" selects this default password, which is returned.
940         */
941        protected String getPassword(ParamModel opt, String defaultPassword,
942                boolean create) throws CommandValidationException {
943    
944            String passwordName = opt.getName();
945            String password = passwords.get(passwordName);
946            if (password != null)
947                return password;
948    
949            if (opt.getParam().optional())
950                return null;        // not required
951    
952            if (!programOpts.isInteractive())
953                return null;        // can't prompt for it
954    
955            String description = null;
956            if (opt instanceof ParamModelData)
957                description = ((ParamModelData)opt).getDescription();
958            String newprompt;
959            if (ok(description)) {
960                if (defaultPassword != null) {
961                    if (defaultPassword.length() == 0)
962                        newprompt =
963                            strings.get("NewPasswordDescriptionDefaultEmptyPrompt",
964                                                description);
965                    else
966                        newprompt =
967                            strings.get("NewPasswordDescriptionDefaultPrompt",
968                                                description, defaultPassword);
969                } else
970                    newprompt =
971                        strings.get("NewPasswordDescriptionPrompt", description);
972            } else {
973                if (defaultPassword != null) {
974                    if (defaultPassword.length() == 0)
975                        newprompt =
976                            strings.get("NewPasswordDefaultEmptyPrompt",
977                                                passwordName);
978                    else
979                        newprompt =
980                            strings.get("NewPasswordDefaultPrompt",
981                                                passwordName, defaultPassword);
982                } else
983                    newprompt = strings.get("NewPasswordPrompt", passwordName);
984            }
985    
986            String newpassword = readPassword(newprompt);
987    
988            /*
989             * If we allow for a default password, and the user just hit "Enter",
990             * return the default password.  No need to prompt twice or check
991             * for validity.
992             */
993            if (defaultPassword != null) {
994                if (newpassword == null)
995                    newpassword = "";
996                if (newpassword.length() == 0) {
997                    newpassword = defaultPassword;
998                    passwords.put(passwordName, newpassword);
999                    return newpassword;
1000                }
1001            }
1002    
1003            /*
1004             * If not creating a new password, don't need to verify that
1005             * the user typed it correctly by making them type it twice,
1006             * and don't need to check it for validity.  Just return what
1007             * we have.
1008             */
1009            if (!create) {
1010                passwords.put(passwordName, newpassword);
1011                return newpassword;
1012            }
1013    
1014            String confirmationPrompt;
1015            if (ok(description)) {
1016                confirmationPrompt =
1017                    strings.get("NewPasswordDescriptionConfirmationPrompt",
1018                                description);
1019            } else {
1020                confirmationPrompt =
1021                    strings.get("NewPasswordConfirmationPrompt", passwordName);
1022            }
1023            String newpasswordAgain = readPassword(confirmationPrompt);
1024            if (!newpassword.equals(newpasswordAgain)) {
1025                throw new CommandValidationException(
1026                    strings.get("OptionsDoNotMatch",
1027                                ok(description) ? description : passwordName));
1028            }
1029            passwords.put(passwordName, newpassword);
1030            return newpassword;
1031        }
1032    
1033        /**
1034         * Display the given prompt and read a password without echoing it.
1035         * Returns null if no console available.
1036         */
1037        protected String readPassword(String prompt) {
1038            String password = null;
1039            Console cons = System.console();
1040            if (cons != null) {
1041                char[] pc = cons.readPassword("%s", prompt);
1042                // yes, yes, yes, it would be safer to not keep it in a String
1043                password = new String(pc);
1044            }
1045            return password;
1046        }
1047    
1048        /**
1049         * Get the ParamModel that corresponds to the operand
1050         * (primary parameter).  Return null if none.
1051         */
1052        protected ParamModel getOperandModel() {
1053            for (ParamModel pm : commandModel.getParameters()) {
1054                if (pm.getParam().primary())
1055                    return pm;
1056            }
1057            return null;
1058        }
1059    
1060        /**
1061         * Get an option value, that might come from the command line
1062         * or from the environment.  Return the default value for the
1063         * option if not otherwise specified.
1064         */
1065        protected String getOption(String name) {
1066            String val = options.getOne(name);
1067            if (val == null)
1068                val = env.getStringOption(name);
1069            if (val == null) {
1070                // no value, find the default
1071                ParamModel opt = commandModel.getModelFor(name);
1072                // if no value was specified and there's a default value, return it
1073                if (opt != null) {
1074                    String def = opt.getParam().defaultValue();
1075                    if (ok(def))
1076                        val = def;
1077                }
1078            }
1079            return val;
1080        }
1081    
1082        /**
1083         * Get a boolean option value, that might come from the command line
1084         * or from the environment.
1085         */
1086        protected boolean getBooleanOption(String name) {
1087            String val = getOption(name);
1088            return val != null && Boolean.parseBoolean(val);
1089        }
1090    
1091        /**
1092         * Return the named system property, or property
1093         * set in asenv.conf.
1094         */
1095        protected String getSystemProperty(String name) {
1096            return systemProps.get(name);
1097        }
1098    
1099        /**
1100         * Return all the system properties and properties set
1101         * in asenv.conf.  The returned Map may not be modified.
1102         */
1103        protected Map<String,String> getSystemProperties() {
1104            return systemProps;
1105        }
1106    
1107        /**
1108         * If this is an unsupported command, throw an exception.
1109         */
1110        private static void checkUnsupportedLegacyCommand(String cmd)
1111                throws CommandException {
1112            for (String c : unsupported) {
1113                if (c.equals(cmd)) {
1114                    throw new CommandException(
1115                                strings.get("UnsupportedLegacyCommand", cmd));
1116                }
1117            }
1118            // it is a supported command; do nothing
1119        }
1120    
1121        /**
1122         * Prints the exception message with level as FINER.
1123         *
1124         * @param e the exception object to print
1125         */
1126        protected void printExceptionStackTrace(java.lang.Throwable e) {
1127            if (!logger.isLoggable(Level.FINER))
1128                return;
1129            /*
1130            java.lang.StackTraceElement[] ste = e.getStackTrace();
1131            for (int ii = 0; ii < ste.length; ii++)
1132                printDebugMessage(ste[ii].toString());
1133            */
1134            final ByteArrayOutputStream output = new ByteArrayOutputStream(512);
1135            e.printStackTrace(new java.io.PrintStream(output));
1136            try {
1137                output.close();
1138            } catch (IOException ex) {
1139                // ignore
1140            }
1141            logger.finer(output.toString());
1142        }
1143    
1144        protected static boolean ok(String s) {
1145            return s != null && s.length() > 0;
1146        }
1147    
1148        // shorthand for this too-verbose operation
1149        private static String lc(String s) {
1150            return s.toLowerCase(Locale.ENGLISH);
1151        }
1152    
1153        /**
1154         * Read the named resource file and add the first token on each line
1155         * to the set.  Skip comment lines.
1156         */
1157        private static void file2Set(String file, Set<String> set) {
1158            BufferedReader reader = null;
1159            try {
1160                InputStream is = CLICommand.class.getClassLoader().
1161                                    getResourceAsStream(file);
1162                if (is == null)
1163                    return;     // in case the resource doesn't exist
1164                reader = new BufferedReader(new InputStreamReader(is));
1165                String line;
1166                while ((line = reader.readLine()) != null) {
1167                    if (line.startsWith("#"))
1168                        continue; // # indicates comment
1169                    StringTokenizer tok = new StringTokenizer(line, " ");
1170                    // handles with or without space, rudimendary as of now
1171                    String cmd = tok.nextToken();
1172                    set.add(cmd);
1173                }
1174            } catch (IOException e) {
1175                e.printStackTrace();
1176            } finally {
1177                if (reader != null) {
1178                    try {
1179                        reader.close();
1180                    } catch (IOException ee) {
1181                        // ignore
1182                    }
1183    
1184                }
1185            }
1186        }
1187    }