name.livitski.tools.springlet.Launcher.java Source code

Java tutorial

Introduction

Here is the source code for name.livitski.tools.springlet.Launcher.java

Source

/**
 *    Copyright  2013 Konstantin "Stan" Livitski
 * 
 *    This file is part of Springlet. Springlet is
 *    licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package name.livitski.tools.springlet;

import java.io.InputStream;
import java.util.Arrays;
import java.util.ListIterator;

import org.apache.commons.logging.Log;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

/**
 * Instantiates and runs the application bean as configured.
 * The framework reads the application's configuration from
 * {@link #MAIN_BEAN_CONFIG_FILE}. First, it tries to
 * {@link #withArguments(String[]) parse the command line}.
 * After successful parsing, the framework looks for the
 * {@link #BEAN_NAME_MAIN main application bean} there and tries
 * to {@link #run() launch it}.
 */
public class Launcher extends Logging {
    /**
     * The name of a file at the root of the project's classpath
     * that configures the main bean to be launched.
     */
    public static final String MAIN_BEAN_CONFIG_FILE = "springlet.xml";

    /**
     * Exit code <code>1</code> indicates the application's linkage
     * problem or an unhandled exception.
     */
    public static final int STATUS_INTERNAL_ERROR = 1;

    /**
     * Exit code <code>2</code> indicates a problem with the command
     * line syntax or invalid argument(s).
     */
    public static final int STATUS_COMMAND_PARSING_FAILURE = 2;

    /**
     * Exit code <code>3</code> indicates that application run has
     * been skipped, e.g. to display the usage information.
     */
    public static final int STATUS_RUN_SKIPPED = 3;

    /**
     * The number of exit codes reserved by the framework.  
     */
    public static final int RESERVED_EXITCODE_MAX = STATUS_RUN_SKIPPED;

    /**
     * Bean name prefix for long commands.  
     */
    public static final String BEAN_NAME_PREFIX_COMMAND = "command-";

    /**
     * Bean name prefix for short commands (switches).  
     */
    public static final String BEAN_NAME_PREFIX_SWITCH = "switch-";

    /**
     * Bean name of the default handler for unclaimed arguments.
     * The default handler, if any, must be an instance of {@link Command}
     * that will receive an iterator over unrecognized argument(s).
     */
    public static final String BEAN_NAME_DEFAULT_HANDLER = "handler-default";

    /**
     * Name of the main {@link ApplicationBean application bean} to launch.
     */
    public static final String BEAN_NAME_MAIN = "main";

    /**
     * The name of a file at the root of the project's classpath
     * that configures the java.util.logging facility.
     */
    public static final String LOG_PROPERTIES_FILE = "jdk-log.default.properties";

    /**
     * Configures the manager using command-line arguments.
     * The arguments are checked in their command line sequence.
     * If an argument begins with {@link Command#COMMAND_PREFIX},
     * its part that follows it is treated as a (long) command's name.
     * If an argument begins with {@link Command#SWITCH_PREFIX},
     * the following character(s) are treated as a switch.
     * The name or switch extracted this way, prepended with
     * a bean name prefix for a {@link #BEAN_NAME_PREFIX_COMMAND command}
     * or a {@link #BEAN_NAME_PREFIX_SWITCH switch}, becomes the name
     * of a bean to look up in the framework's
     * {@link #MAIN_BEAN_CONFIG_FILE configuration file(s)}.
     * The bean that handles a command must extend the
     * {@link Command} class. Once a suitable bean is found,
     * it is called to act upon the command or switch and process
     * any additional arguments associated with it. 
     * If no bean can be found or the argument is not prefixed
     * properly, it is considered unclaimed. You may configure a
     * special subclass of {@link Command} to process such arguments
     * by placing a bean named {@link #BEAN_NAME_DEFAULT_HANDLER} on
     * the configuration. If there is no such bean configured, or when
     * any {@link Command} bean throws an exception while processing
     * a command, a command line error is reported and the application
     * quits.
     * @param args the command line
     * @return this manager object
     */
    public Launcher withArguments(String[] args) {
        configureDefaultLogging();
        final Log log = log();
        ListIterator<String> iargs = Arrays.asList(args).listIterator();
        while (iargs.hasNext()) {
            String arg = iargs.next();
            String prefix = null;
            String beanPrefix = null;
            Command cmd = null;
            if (arg.startsWith(Command.COMMAND_PREFIX))
                prefix = Command.COMMAND_PREFIX;
            else if (arg.startsWith(Command.SWITCH_PREFIX))
                prefix = Command.SWITCH_PREFIX;
            if (null != prefix) {
                arg = arg.substring(prefix.length());
                beanPrefix = Command.SWITCH_PREFIX == prefix ? BEAN_NAME_PREFIX_SWITCH : BEAN_NAME_PREFIX_COMMAND;
                try {
                    cmd = getBeanFactory().getBean(beanPrefix + arg, Command.class);
                } catch (NoSuchBeanDefinitionException noBean) {
                    log.debug("Could not find a handler for command-line argument " + prefix + arg, noBean);
                }
            }
            if (null == cmd) {
                iargs.previous();
                try {
                    cmd = getBeanFactory().getBean(BEAN_NAME_DEFAULT_HANDLER, Command.class);
                } catch (RuntimeException ex) {
                    log.error("Unknown command line argument: " + (null != prefix ? prefix : "") + arg, ex);
                    status = STATUS_COMMAND_PARSING_FAILURE;
                    break;
                }
            }
            try {
                cmd.process(iargs);
            } catch (SkipApplicationRunRequest skip) {
                if (log.isTraceEnabled())
                    log.trace("Handler for argument " + (null != prefix ? prefix : "") + arg
                            + " requested to skip the application run", skip);
                status = STATUS_RUN_SKIPPED;
                break;
            } catch (RuntimeException err) {
                log.error("Invalid command line argument(s) near " + (null != prefix ? prefix : "") + arg + ": "
                        + err.getMessage(), err);
                status = STATUS_COMMAND_PARSING_FAILURE;
                break;
            } catch (ApplicationBeanException err) {
                log.error("Error processing command line argument " + (null != prefix ? prefix : "") + arg + ": "
                        + err.getMessage(), err);
                try {
                    err.updateBeanStatus();
                } catch (RuntimeException noStatus) {
                    final ApplicationBean appBean = err.getApplicationBean();
                    log.warn("Could not obtain status code" + (null != appBean ? " from " + appBean : ""),
                            noStatus);
                    status = STATUS_INTERNAL_ERROR;
                }
                break;
            }
        }
        return this;
    }

    public void run() {
        configureDefaultLogging();
        try {
            final ApplicationBean applicationBean = getApplicationBean();
            if (null == applicationBean)
                status = STATUS_INTERNAL_ERROR;
            else
                applicationBean.run();
        } catch (RuntimeException error) {
            log().error("Unhandled exception: " + error.getMessage(), error);
            status = STATUS_INTERNAL_ERROR;
        }
    }

    public int getStatusCode() {
        if (0 == status) {
            final ApplicationBean applicationBean = getApplicationBean();
            if (null == applicationBean)
                status = STATUS_INTERNAL_ERROR;
            else {
                status = applicationBean.getStatusCode();
                if (0 < status)
                    status += RESERVED_EXITCODE_MAX;
                else if (0 > status) {
                    log().error("Application bean " + applicationBean.getClass() + " returned invalid status: "
                            + status);
                    status = STATUS_INTERNAL_ERROR;
                }
            }
        }
        return status;
    }

    public Launcher() {
    }

    protected ApplicationBean getApplicationBean() {
        if (null == appBean)
            try {
                appBean = getBeanFactory().getBean(BEAN_NAME_MAIN, ApplicationBean.class);
            } catch (RuntimeException error) {
                log().fatal("Error initializing the application", error);
            }
        return appBean;
    }

    protected BeanFactory getBeanFactory() {
        if (null == beanFactory) {
            beanFactory = new DefaultListableBeanFactory();
            final XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(beanFactory);
            xmlReader.setValidating(false);
            xmlReader.loadBeanDefinitions(MAIN_BEAN_CONFIG_FILE);
        }
        return beanFactory;
    }

    protected void configureJDKLogging() {
        InputStream cfg;
        if (null == System.getProperty("java.util.logging.config.class")
                && null == System.getProperty("java.util.logging.config.file")
                && null != (cfg = getClass().getResourceAsStream('/' + LOG_PROPERTIES_FILE))) {
            try {
                configureJDKLogs(cfg);
            } catch (Exception e) {
                System.err.println(
                        "WARNING: could not configure JDK logging. Detailed diagnostics may not be available. "
                                + e);
            } finally {
                try {
                    cfg.close();
                    cfg = null;
                } catch (Exception ignored) {
                }
            }
        }
    }

    /**
     * A check mark to tell this object that it has configured logging
     * for the application or that it should not configure any logging.
     * @see #configureJDKLogging() 
     */
    protected static boolean isLoggingConfigured;

    /**
     * Configures the default logging for this application if necessary.
     */
    private void configureDefaultLogging() {
        if (!isLoggingConfigured) {
            configureJDKLogging();
            isLoggingConfigured = true;
        }
    }

    private int status;
    private ApplicationBean appBean;
    private DefaultListableBeanFactory beanFactory;
}