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.net.URISyntaxException;
045    import java.text.*;
046    import java.util.*;
047    import java.util.logging.*;
048    import org.glassfish.common.util.admin.AsadminInput;
049    
050    import org.jvnet.hk2.annotations.Contract;
051    import org.jvnet.hk2.component.*;
052    import org.glassfish.api.admin.*;
053    import com.sun.enterprise.module.*;
054    import com.sun.enterprise.module.single.StaticModulesRegistry;
055    
056    import com.sun.enterprise.admin.cli.remote.*;
057    import com.sun.enterprise.universal.i18n.LocalStringsImpl;
058    import com.sun.enterprise.universal.io.SmartFile;
059    import com.sun.enterprise.universal.glassfish.ASenvPropertyReader;
060    import com.sun.enterprise.util.JDK;
061    import com.sun.enterprise.util.SystemPropertyConstants;
062    
063    /**
064     * The asadmin main program.
065     */
066    public class AsadminMain {
067    
068        private       static String classPath;
069        private       static String className;
070        private       static String command;
071        private       static ProgramOptions po;
072        private       static Habitat habitat;
073        private       static Logger logger;
074    
075        private final static int ERROR = 1;
076        private final static int CONNECTION_ERROR = 2;
077        private final static int INVALID_COMMAND_ERROR = 3;
078        private final static int SUCCESS = 0;
079        private final static int WARNING = 4;
080    
081        // XXX - move this to LogDomains?
082        private final static String ADMIN_CLI_LOGGER =
083                                        "com.sun.enterprise.admin.cli";
084    
085        private final static String DEBUG_FLAG = "Debug";
086        private final static String ENV_DEBUG_FLAG = "AS_DEBUG";
087    
088        private static final String[] copyProps = {
089            SystemPropertyConstants.INSTALL_ROOT_PROPERTY,
090            SystemPropertyConstants.CONFIG_ROOT_PROPERTY,
091            SystemPropertyConstants.PRODUCT_ROOT_PROPERTY
092        };
093    
094        private static final LocalStringsImpl strings =
095                                    new LocalStringsImpl(AsadminMain.class);
096    
097        static {
098            Map<String, String> systemProps = new ASenvPropertyReader().getProps();
099            for (String prop : copyProps) {
100                String val = systemProps.get(prop);
101                if (ok(val))
102                    System.setProperty(prop, val);
103            }
104        }
105    
106        /**
107         * A ConsoleHandler that prints all non-SEVERE messages to System.out
108         * and all SEVERE messages to System.err.
109         */
110        private static class CLILoggerHandler extends ConsoleHandler {
111            
112            private CLILoggerHandler() {
113                setFormatter(new CLILoggerFormatter());
114            }
115            
116            @Override
117            public void publish(java.util.logging.LogRecord logRecord) {
118                if (!isLoggable(logRecord))
119                    return;
120                final PrintStream ps = (logRecord.getLevel() == Level.SEVERE) ? System.err : System.out;
121                ps.println(getFormatter().format(logRecord));
122            }
123        }
124        
125        private static class CLILoggerFormatter extends SimpleFormatter {
126    
127            @Override
128            public synchronized String format(LogRecord record) {
129                return formatMessage(record);
130            }
131        }
132    
133        public static void main(String[] args) {
134            int minor = JDK.getMinor();
135    
136            if (minor < 6) {
137                System.err.println(strings.get("OldJdk", "" + minor));
138                System.exit(ERROR);
139            }
140    
141            // bnevins 4-18-08 A quickly added trace. should clean up later.
142            // TODO TODO TODO TODO
143    
144            // System Prop just needs to exist
145            // Env Var. needs to be set to "true"
146            String sys = System.getProperty(DEBUG_FLAG);
147            boolean env = Boolean.parseBoolean(System.getenv(ENV_DEBUG_FLAG));
148            boolean trace = Boolean.parseBoolean(System.getenv("AS_TRACE"));
149            boolean debug = sys != null || env;
150    
151            /*
152             * Use a logger associated with the top-most package that we
153             * expect all admin commands to share.  Only this logger and
154             * its children obey the conventions that map terse=false to
155             * the INFO level and terse=true to the FINE level.
156             */
157            logger = Logger.getLogger(ADMIN_CLI_LOGGER);
158            if (trace)
159                logger.setLevel(Level.FINEST);
160            else if (debug)
161                logger.setLevel(Level.FINER);
162            else {
163                logger.setLevel(Level.FINE);
164            }
165            logger.setUseParentHandlers(false);
166            Handler h = new CLILoggerHandler();
167            h.setLevel(logger.getLevel());
168            logger.addHandler(h);
169    
170            // make sure the root logger uses our handler as well
171            Logger rlogger = Logger.getLogger("");
172            rlogger.setUseParentHandlers(false);
173            for (Handler lh : rlogger.getHandlers())
174                rlogger.removeHandler(lh);
175            rlogger.addHandler(h);
176    
177            if (CLIConstants.debugMode) {
178                System.setProperty(CLIConstants.WALL_CLOCK_START_PROP,
179                    "" + System.currentTimeMillis());
180                logger.finer("CLASSPATH= " +
181                        System.getProperty("java.class.path") +
182                        "\nCommands: " + Arrays.toString(args));
183            }
184    
185            /*
186             * Create a ClassLoader to load from all the jar files in the
187             * lib/asadmin directory.  This directory can contain extension
188             * jar files with new local asadmin commands.
189             */
190            ClassLoader ecl = AsadminMain.class.getClassLoader();
191            try {
192                File inst = new File(System.getProperty(
193                                    SystemPropertyConstants.INSTALL_ROOT_PROPERTY));
194                File ext = new File(new File(inst, "lib"), "asadmin");
195                logger.finer(
196                                        "asadmin extension directory: " + ext);
197                if (ext.isDirectory())
198                    ecl = new DirectoryClassLoader(ext, ecl);
199                else
200                    logger.info(
201                                                strings.get("ExtDirMissing", ext));
202            } catch (IOException ex) {
203                // any failure here is fatal
204                logger.info(
205                                        strings.get("ExtDirFailed", ex));
206                System.exit(1);
207            }
208    
209            /*
210             * Set the thread's context class laoder so that everyone can
211             * load from our extension directory.
212             */
213            Thread.currentThread().setContextClassLoader(ecl);
214    
215            /*
216             * Create a habitat that can load from the extension directory.
217             */
218            ModulesRegistry registry = new StaticModulesRegistry(ecl);
219            habitat = registry.createHabitat("default");
220    
221            classPath =
222                SmartFile.sanitizePaths(System.getProperty("java.class.path"));
223            className = AsadminMain.class.getName();
224    
225            /*
226             * Special case: no arguments is the same as "multimode".
227             */
228            if (args.length == 0)
229                 args = new String[] { "multimode" };
230    
231            /*
232             * Special case: -V argument is the same as "version".
233             */
234            if (args[0].equals("-V"))
235                 args = new String[] { "version" };
236    
237            command = args[0];
238            int exitCode = executeCommand(args);
239    
240            switch (exitCode) {
241            case SUCCESS:
242                if (!po.isTerse())
243                    logger.fine(
244                        strings.get("CommandSuccessful", command));
245                break;
246    
247            case WARNING:
248                logger.fine(
249                    strings.get("CommandSuccessfulWithWarnings", command));
250                exitCode = SUCCESS;
251                break;
252    
253            case ERROR:
254                logger.fine(
255                    strings.get("CommandUnSuccessful", command));
256                break;
257    
258            case INVALID_COMMAND_ERROR:
259                logger.fine(
260                    strings.get("CommandUnSuccessful", command));
261                break;
262    
263            case CONNECTION_ERROR:
264                logger.fine(
265                    strings.get("CommandUnSuccessful", command));
266                break;
267            }
268            CLIUtil.writeCommandToDebugLog(args, exitCode);
269            System.exit(exitCode);
270        }
271    
272        public static int executeCommand(String[] argv) {
273            CLICommand cmd = null;
274            Environment env = new Environment();
275            try {
276    
277                // if the first argument is an option, we're using the new form
278                if (argv.length > 0 && argv[0].startsWith("-")) {
279                    /*
280                     * Parse all the asadmin options, stopping at the first
281                     * non-option, which is the command name.
282                     */
283                    Parser rcp = new Parser(argv, 0,
284                                    ProgramOptions.getValidOptions(), false);
285                    ParameterMap params = rcp.getOptions();
286                    po = new ProgramOptions(params, env);
287                    readAndMergeOptionsFromAuxInput(po);
288                    List<String> operands = rcp.getOperands();
289                    argv = operands.toArray(new String[operands.size()]);
290                } else
291                    po = new ProgramOptions(env);
292                po.toEnvironment(env);
293                po.setClassPath(classPath);
294                po.setClassName(className);
295                if (argv.length == 0) {
296                    if (po.isHelp())
297                        argv = new String[] { "help" };
298                    else
299                        argv = new String[] { "multimode" };
300                }
301                command = argv[0];
302    
303                habitat.addComponent("environment", env);
304                habitat.addComponent("program-options", po);
305                cmd = CLICommand.getCommand(habitat, command);
306                return cmd.execute(argv);
307            } catch (CommandValidationException cve) {
308                logger.severe(cve.getMessage());
309                if (cmd == null)    // error parsing program options
310                    printUsage();
311                else
312                    logger.severe(cmd.getUsage());
313                return ERROR;
314            } catch (InvalidCommandException ice) {
315                // find closest match with local or remote commands
316                logger.severe(ice.getMessage());
317                try {
318                    CLIUtil.displayClosestMatch(command,
319                        CLIUtil.getAllCommands(habitat, po, env),
320                        strings.get("ClosestMatchedLocalAndRemoteCommands"), logger);
321                } catch (InvalidCommandException e) {
322                    // not a big deal if we cannot help
323                }
324                return ERROR;
325            } catch (CommandException ce) {
326                if (ce.getCause() instanceof java.net.ConnectException) {
327                    // find closest match with local commands
328                    logger.severe(ce.getMessage());
329                    try {
330                        CLIUtil.displayClosestMatch(command,
331                            CLIUtil.getLocalCommands(habitat),
332                            strings.get("ClosestMatchedLocalCommands"), logger);
333                    } catch (InvalidCommandException e) {
334                        logger.info(
335                                strings.get("InvalidRemoteCommand", command));
336                    }
337                } else
338                    logger.severe(ce.getMessage());
339                return ERROR;
340            }
341        }
342    
343        private static void readAndMergeOptionsFromAuxInput(final ProgramOptions progOpts) 
344                throws CommandException {
345            final String auxInput = progOpts.getAuxInput();
346            if (auxInput == null || auxInput.length() == 0) {
347                return;
348            }
349            final ParameterMap newParamMap = new ParameterMap();
350            /*
351             * We will place the options passed via the aux. input on the command
352             * line and we do not want to repeat the read from stdin again, so
353             * remove the aux input setting.
354             */
355            progOpts.setAuxInput(null);
356            try {
357                final AsadminInput.InputReader reader = AsadminInput.reader(auxInput);
358                final Properties newOptions = reader.settings().get("option");
359                for (String propName : newOptions.stringPropertyNames()) {
360                    newParamMap.add(propName, newOptions.getProperty(propName));
361                }
362                progOpts.updateOptions(newParamMap);
363            } catch (Exception ex) {
364                throw new RuntimeException(ex);
365            }
366        }
367        
368        /**
369         * Print usage message for asadmin command.
370         * XXX - should be derived from ProgramOptions.
371         */
372        private static void printUsage() {
373            logger.severe(strings.get("Asadmin.usage"));
374        }
375    
376        private static boolean ok(String s) {
377            return s!= null && s.length() > 0;
378        }
379    
380        /** Turned off for now -- it takes ~200 msec on a laptop!
381        private final static boolean foundClass(String s) {
382            try {
383                Class.forName(s);
384                return true;
385            } catch (Throwable t) {
386                System.out.println("Can not find class: " + s);
387                return false;
388            }
389        }
390    
391        private final static String[] requiredClassnames = {
392            // one from launcher jar        
393            "com.sun.enterprise.admin.launcher.GFLauncher",
394            // one from universal jar
395            "com.sun.enterprise.universal.xml.MiniXmlParser",
396            // one from glassfish bootstrap jar
397            "com.sun.enterprise.glassfish.bootstrap.ASMain",
398            // one from stax-api
399            "javax.xml.stream.XMLInputFactory",
400            // one from server-mgmt
401            "com.sun.enterprise.admin.servermgmt.RepositoryException",
402            // one from common-utils
403            "com.sun.enterprise.util.net.NetUtils",
404            // one from admin/util
405            "com.sun.enterprise.admin.util.TokenValueSet",
406            // here's one that server-mgmt is dependent on
407            "com.sun.enterprise.security.auth.realm.file.FileRealm",
408            // dol
409            "com.sun.enterprise.deployment.PrincipalImpl",
410            // kernel
411            //"com.sun.appserv.server.util.Version",
412        };
413    
414        static {
415            // check RIGHT NOW to make sure all the classes we need are
416            // available
417            long start = System.currentTimeMillis();
418            boolean gotError = false;
419            for (String s : requiredClassnames) {
420                if (!foundClass(s))
421                    gotError = true;
422            }
423            // final test -- see if sjsxp is available
424            try {
425                javax.xml.stream.XMLInputFactory.newInstance().getXMLReporter();
426            } catch(Throwable t) {
427                gotError = true;
428                System.out.println("Can't access STAX classes");
429            }
430            if (gotError) {
431                // messages already sent to stdout...
432                System.exit(1);
433            }
434            long stop = System.currentTimeMillis();
435            System.out.println("Time to pre-load classes = " + (stop-start) + " msec");
436        }
437         */
438    }