/*
* Copyright 2010 Outerthought bvba
*
* 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 org.lilyproject.cli;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
import org.lilyproject.util.exception.StackTracePrinter;
/**
* Base framework for Lily CLI tools. Purpose is to have some uniformity in the CLI tools and to avoid
* code duplication.
*
* Subclasses can override:
* <ul>
* <li>{@link #getCmdName()}
* <li>{@link #getOptions()}
* <li>{@link #processOptions(org.apache.commons.cli.CommandLine)}
* <li>{@link #run(org.apache.commons.cli.CommandLine)}
* <li>{@link #reportThrowable(Throwable)}
* </ul>
*/
public abstract class BaseCliTool {
protected Option helpOption;
protected Option versionOption;
protected Option logConfOption;
protected Option dumpLogConfOption;
private Options cliOptions;
protected void start(String[] args) {
int result = 1;
try {
System.out.println();
result = runBase(args);
} catch (CliException e) {
System.err.println();
System.err.println(e.getMessage());
System.exit(e.getExitCode());
} catch (Throwable t) {
reportThrowable(t);
} finally {
try {
cleanup();
} catch (Throwable t) {
System.err.println("Error during cleanup:");
t.printStackTrace();
}
}
System.out.println();
if (result != 0) {
System.exit(result);
}
}
protected void reportThrowable(Throwable throwable) {
StackTracePrinter.printStackTrace(throwable);
}
/**
* Return the CLI options. When overriding, call super and add your own
* options.
*/
public List<Option> getOptions() {
List<Option> options = new ArrayList<Option>();
helpOption = new Option("h", "help", false, "Shows help");
options.add(helpOption);
versionOption = new Option("v", "version", false, "Shows the version");
options.add(versionOption);
logConfOption = OptionBuilder
.withArgName("config")
.hasArg()
.withDescription("log4j config file (.properties or .xml)")
.create("log");
options.add(logConfOption);
dumpLogConfOption = OptionBuilder
.withDescription("Dump default log4j configuration")
.create("dumplog");
options.add(dumpLogConfOption);
return options;
}
/**
* The name of this CLI tool, used in the help message.
*/
protected abstract String getCmdName();
/**
* Override this to perform cleanup after the tool has run, also in case
* an exception occurred (= called from a finally block).
*/
protected void cleanup() {
}
/**
* Process option values, typically this performs basic stuff like reading
* the option value and validating it. First always call super, if non-zero
* is returned, then return this value immediately.
*/
protected int processOptions(CommandLine cmd) throws Exception {
if (cmd.hasOption(helpOption.getOpt())) {
printHelp();
return 1;
}
if (cmd.hasOption(versionOption.getOpt())) {
System.out.println(getVersion());
return 1;
}
if (cmd.hasOption(dumpLogConfOption.getOpt())) {
IOUtils.copy(BaseCliTool.class.getResourceAsStream("log4j.properties"), System.out);
return 1;
}
File logConfFile = null;
if (cmd.hasOption(logConfOption.getOpt())) {
logConfFile = new File(cmd.getOptionValue(logConfOption.getOpt()));
if (!logConfFile.exists()) {
System.err.println("Specified log4j configuration file does not exist:");
System.err.println(logConfFile);
}
}
setupLogging(logConfFile);
return 0;
}
/**
* Perform the actual action. First always call super, if non-zero is returned,
* then return this value immediately.
*/
public int run(CommandLine cmd) throws Exception {
return 0;
}
private void setupLogging(File logConfFile) {
// Reset any configuration log4j might already have loaded (from classpath, cwd, ...).
LogManager.resetConfiguration();
// If the user did not specify a configuration file, default to log4j.properties in the working dir
if (logConfFile == null) {
File defaultConf = new File("log4j.properties");
if (defaultConf.exists()) {
logConfFile = defaultConf;
// printing to err so that this isn't included when stdout is redirected to a file
System.err.println("Using log4j.properties from working directory: " + logConfFile.getAbsolutePath());
}
}
if (logConfFile == null) {
// Use the built-in config
PropertyConfigurator.configure(BaseCliTool.class.getResource("log4j.properties"));
} else if (logConfFile.getName().endsWith(".xml")) {
DOMConfigurator.configure(logConfFile.getAbsolutePath());
} else {
PropertyConfigurator.configure(logConfFile.getAbsolutePath());
}
}
private int runBase(String[] args) throws Exception {
//
// Set up options
//
cliOptions = new Options();
for (Option option : getOptions()) {
cliOptions.addOption(option);
}
//
// Parse options
//
CommandLineParser parser = new PosixParser();
CommandLine cmd;
try {
cmd = parser.parse(cliOptions, args);
} catch (ParseException e) {
System.out.println(e.getMessage());
System.out.println();
printHelp();
return 1;
}
//
// Process options
//
int result = processOptions(cmd);
if (result != 0) {
return result;
}
//
// Run tool
//
return run(cmd);
}
protected void printHelp() throws IOException {
printHelpHeader();
HelpFormatter help = new HelpFormatter();
help.printHelp(getCmdName(), cliOptions, true);
printHelpFooter();
}
protected void printHelpHeader() throws IOException {
String className = getClass().getName();
String helpHeaderPath = className.replaceAll(Pattern.quote("."), "/") + "_help_header.txt";
InputStream is = getClass().getClassLoader().getResourceAsStream(helpHeaderPath);
if (is != null) {
IOUtils.copy(is, System.out);
System.out.println();
System.out.println();
}
}
protected void printHelpFooter() throws IOException {
String className = getClass().getName();
String helpHeaderPath = className.replaceAll(Pattern.quote("."), "/") + "_help_footer.txt";
InputStream is = getClass().getClassLoader().getResourceAsStream(helpHeaderPath);
if (is != null) {
System.out.println();
IOUtils.copy(is, System.out);
System.out.println();
}
}
protected abstract String getVersion();
}