com.linkedin.databus.core.BaseCli.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.databus.core.BaseCli.java

Source

package com.linkedin.databus.core;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.PropertyConfigurator;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/

/**
 * Base CLI implementation. Provides common options.
 * Default options supported:
 * <preformat>
 * -h,--help                        Prints command-line options info
 * -l,--log_props <property_file>   Log4j properties to use
 * -q,--quiet                       turn off logging
 * -v                               verbose
 * -vv                              more verbose
 * -vvv                             most verbose
 * </preformat>
 * */
public class BaseCli {
    /** Possible verbosities for logging - get around the problem that the int <--> Level mapping
     * is not consistent */
    protected static final Level[] VERBOSITIES = { Level.OFF, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG,
            Level.ALL };

    /**
     * Encapsulates the information to be printed as part of the help screen for a CLI tool.
     * The structure is:
     * <pre>
     * usage
     * header
     * options (generate by Apache CLI)
     * footer
     * </pre>
     */
    public static class CliHelp {
        private final String _className;
        private final String _usage;
        private final String _header;
        private final String _footer;

        public CliHelp(String className, String usage, String header, String footer) {
            super();
            _usage = usage;
            _header = header;
            _footer = footer;
            _className = className;
        }

        public String getUsage() {
            return _usage;
        }

        public String getHeader() {
            return _header;
        }

        public String getFooter() {
            return _footer;
        }

        public String getClassName() {
            return _className;
        }
    }

    public static interface HeaderFooterBuilder {
        HeaderFooterBuilder add(String s);

        HeaderFooterBuilder addLine();

        HeaderFooterBuilder addLine(String ln);

        HeaderFooterBuilder addSection(String sectionName);

        CliHelpBuilder finish();

        @Override
        String toString();
    }

    public static class StdHeaderFooterBuilder implements HeaderFooterBuilder {

        protected final StringBuilder _s = new StringBuilder();
        protected final CliHelpBuilder _parent;

        public StdHeaderFooterBuilder(CliHelpBuilder parent) {
            super();
            _parent = parent;
        }

        @Override
        public HeaderFooterBuilder addLine(String ln) {
            _s.append(ln);
            _s.append("\n");
            return this;
        }

        @Override
        public HeaderFooterBuilder addSection(String sectionName) {
            addLine();
            addLine(sectionName.toUpperCase());
            for (int i = 0; i < sectionName.length(); ++i)
                add("-");
            addLine();
            return this;
        }

        @Override
        public String toString() {
            return _s.toString();
        }

        /**
         * @see com.linkedin.databus.core.BaseCli.HeaderFooterBuilder#add(java.lang.String)
         */
        @Override
        public HeaderFooterBuilder add(String s) {
            _s.append(s);
            return this;
        }

        /**
         * @see com.linkedin.databus.core.BaseCli.HeaderFooterBuilder#finishSection()
         */
        @Override
        public CliHelpBuilder finish() {
            return _parent;
        }

        /**
         * @see com.linkedin.databus.core.BaseCli.HeaderFooterBuilder#addLine()
         */
        @Override
        public HeaderFooterBuilder addLine() {
            //the default Apache Cli HelpFormatter does not like completely blank lines :(
            return addLine("\177");
        }
    }

    public static class CliHelpBuilder {
        private String _usage;
        private HeaderFooterBuilder _header;
        private HeaderFooterBuilder _footer;
        private String _className = "<class>";

        public CliHelpBuilder className(String className) {
            _className = className;
            _header = new StdHeaderFooterBuilder(this);
            _footer = new StdHeaderFooterBuilder(this);
            return this;
        }

        public CliHelpBuilder className(Class<?> clazz) {
            return className(clazz.getName());
        }

        public String className() {
            return _className;
        }

        public HeaderFooterBuilder startHeader() {
            return _header;
        }

        public CliHelpBuilder headerBuilder(HeaderFooterBuilder builder) {
            _header = builder;
            return this;
        }

        public HeaderFooterBuilder startFooter() {
            return _footer;
        }

        public CliHelpBuilder footerBuilder(HeaderFooterBuilder builder) {
            _footer = builder;
            return this;
        }

        public String header() {
            return _header.toString();
        }

        public String footer() {
            return _footer.toString();
        }

        public CliHelpBuilder usage(String usage) {
            _usage = usage;
            return this;
        }

        public String usage() {
            return _usage;
        }

        public CliHelp build() {
            if (null == _usage) {
                _usage = "java " + _className + " [options]";
            }

            return new CliHelp(_className, _usage, null != _header ? _header.toString() : "",
                    null != _footer ? _footer.toString() : "");
        }
    }

    protected static final String CMD_LINE_PROPS_OPT_LONG_NAME = "cmdline_props";
    protected static final char CMD_LINE_PROPS_OPT_CHAR = 'c';
    protected static final String HELP_OPT_LONG_NAME = "help";
    protected static final char HELP_OPT_CHAR = 'h';
    protected static final String LOG4J_PROPS_OPT_LONG_NAME = "log_props";
    protected static final char LOG4J_PROPS_OPT_CHAR = 'l';
    protected static final String PROPS_FILE_OPT_LONG_NAME = "props_file";
    protected static final char PROPS_FILE_OPT_CHAR = 'p';
    protected static final String QUIET_OPT_LONG_NAME = "quiet";
    protected static final char QUIET_OPT_CHAR = 'q';
    protected static final String VERBOSE1_OPT_LONG_NAME = "v";
    protected static final String VERBOSE2_OPT_LONG_NAME = "vv";
    protected static final String VERBOSE3_OPT_LONG_NAME = "vvv";

    private final CliHelp _help;
    final protected Options _cliOptions;
    final protected HelpFormatter _helpFormatter;
    final protected Logger _log;
    protected CommandLine _cmd;
    protected int _defaultLogLevelIdx = 3; //Level.INFO
    final protected Properties _configProps = new Properties(System.getProperties());

    public BaseCli(String usage, Logger log) {
        this(new CliHelpBuilder().usage(usage).build(), log);
    }

    public BaseCli(CliHelp help, Logger log) {
        _log = null == log ? Logger.getLogger(getClass()) : log;
        _help = help;
        _cliOptions = new Options();
        _helpFormatter = new HelpFormatter();
        _helpFormatter.setWidth(150);
    }

    public static String createDefaultUsageString(String className) {
        return "java " + className + " [options]";
    }

    public static String createDefaultUsageString(Class<?> clazz) {
        return createDefaultUsageString(clazz.getName());
    }

    public Properties getConfigProps() {
        return _configProps;
    }

    protected String getProgramName() {
        return _help.getClassName();
    }

    protected void printError(String message, boolean printHelp) {
        System.err.println(getProgramName() + ": " + message);
        if (printHelp) {
            System.out.println();
            printCliHelp();
        }
    }

    private void updatePropsFromCmdLine(String cmdLinePropString) {
        String[] cmdLinePropSplit = cmdLinePropString.split(";");
        for (String s : cmdLinePropSplit) {
            String[] onePropSplit = s.split("=");
            if (onePropSplit.length != 2) {
                _log.error("CMD line property setting " + s + "is not valid!");
            } else {
                _log.info("CMD line Property overwride: " + s);
                _configProps.put(onePropSplit[0], onePropSplit[1]);
            }
        }
    }

    /**
     * Creates command-line options
     */
    @SuppressWarnings("static-access")
    protected void constructCommandLineOptions() {
        _cliOptions.addOption(OptionBuilder.withLongOpt(HELP_OPT_LONG_NAME)
                .withDescription("Prints command-line options info").create(HELP_OPT_CHAR));
        _cliOptions.addOption(OptionBuilder.withLongOpt(CMD_LINE_PROPS_OPT_LONG_NAME)
                .withDescription(
                        "Command-line override for config properties; a " + "semicolon-separated list of key=vale.")
                .hasArg().withArgName("Semicolon_separated_properties").create(CMD_LINE_PROPS_OPT_CHAR));
        _cliOptions.addOption(
                OptionBuilder.withLongOpt(LOG4J_PROPS_OPT_LONG_NAME).withDescription("Log4j properties to use")
                        .hasArg().withArgName("property_file").create(LOG4J_PROPS_OPT_CHAR));
        _cliOptions.addOption(
                OptionBuilder.withLongOpt(PROPS_FILE_OPT_LONG_NAME).withDescription("Config properties file to use")
                        .hasArg().withArgName("property_file").create(PROPS_FILE_OPT_CHAR));
        _cliOptions.addOption(OptionBuilder.withLongOpt(QUIET_OPT_LONG_NAME).withDescription("quiet (no logging)")
                .create(QUIET_OPT_CHAR));
        _cliOptions.addOption(OptionBuilder.withDescription("verbose").create(VERBOSE1_OPT_LONG_NAME));
        _cliOptions.addOption(OptionBuilder.withDescription("more verbose").create(VERBOSE2_OPT_LONG_NAME));
        _cliOptions.addOption(OptionBuilder.withDescription("most verbose").create(VERBOSE3_OPT_LONG_NAME));
    }

    /**
     * Parses the command line arguments
     * @param cliArgs      the command line arguments
     * @return true iff parsing was successful
     */
    public boolean processCommandLineArgs(String[] cliArgs) {
        constructCommandLineOptions();
        CommandLineParser cliParser = new GnuParser();

        _cmd = null;
        try {
            _cmd = cliParser.parse(_cliOptions, cliArgs);
        } catch (ParseException pe) {
            printError("failed to parse command-line options: " + pe.toString(), true);
            return false;
        }

        if (_cmd.hasOption(HELP_OPT_CHAR)) {
            printCliHelp();
            return false;
        }

        int verbosityInc = 0;
        if (_cmd.hasOption(VERBOSE3_OPT_LONG_NAME)) {
            //-vvv is always Level.ALL
            verbosityInc = VERBOSITIES.length;
        } else if (_cmd.hasOption(VERBOSE2_OPT_LONG_NAME)) {
            verbosityInc = 2;
        } else if (_cmd.hasOption(VERBOSE1_OPT_LONG_NAME)) {
            verbosityInc = 1;
        }

        Level effectiveLevel = VERBOSITIES[Math.min(_defaultLogLevelIdx + verbosityInc, VERBOSITIES.length - 1)];
        if (_cmd.hasOption(QUIET_OPT_CHAR)) {
            effectiveLevel = Level.OFF;
        }
        Logger.getRootLogger().setLevel(effectiveLevel);

        if (_cmd.hasOption(LOG4J_PROPS_OPT_CHAR)) {
            String log4jPropFile = _cmd.getOptionValue(LOG4J_PROPS_OPT_CHAR);
            PropertyConfigurator.configure(log4jPropFile);
            _log.debug("Using custom logging settings from file " + log4jPropFile);
        } else {
            PatternLayout defaultLayout = new PatternLayout("%d{ISO8601} +%r [%t] (%p) {%c{1}} %m%n");
            ConsoleAppender defaultAppender = new ConsoleAppender(defaultLayout);

            Logger.getRootLogger().removeAllAppenders();
            Logger.getRootLogger().addAppender(defaultAppender);
            _log.debug("Using default logging settings");
        }

        processProperties();

        return true;
    }

    private void processProperties() {
        if (_cmd.hasOption(PROPS_FILE_OPT_CHAR)) {
            for (String propFile : _cmd.getOptionValues(PROPS_FILE_OPT_CHAR)) {
                _log.info("Loading container config from properties file " + propFile);
                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(propFile);
                    _configProps.load(fis);
                } catch (Exception e) {
                    _log.error("error processing properties; ignoring:" + e.getMessage());
                } finally {
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            //not much to do -- ignore
                        }
                    }
                }
            }
        } else {
            _log.info("Using system properties for container config");
        }

        if (_cmd.hasOption(CMD_LINE_PROPS_OPT_CHAR)) {
            String cmdLinePropString = _cmd.getOptionValue(CMD_LINE_PROPS_OPT_CHAR);
            updatePropsFromCmdLine(cmdLinePropString);
        }
    }

    /** Print out the help for this program */
    public void printCliHelp() {
        _helpFormatter.printHelp(_help.getUsage(), _help.getHeader(), _cliOptions, _help.getFooter());
    }

    /** Gets out the usage for this program*/
    public String getUsage() {
        return _help.getUsage();
    }

    /** For testing */
    public static void main(String[] args) {
        BasicConfigurator.configure();
        BaseCli cli = new BaseCli(new CliHelpBuilder().className(BaseCli.class).startHeader()
                .addSection("Description").addLine("Description Line 1").addLine("Description Line 2")
                .addSection("Options").finish().startFooter().addSection("Examples").addLine("* Example Line 1")
                .addLine("\177\t Example Line 2").addSection("Notes").addLine("Be careful").finish().build(), null);
        cli.processCommandLineArgs(args);
    }

}