org.apache.brooklyn.cli.AbstractMain Maven / Gradle / Ivy
Show all versions of brooklyn-cli Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.cli;
import io.airlift.airline.Arguments;
import io.airlift.airline.Cli;
import io.airlift.airline.Cli.CliBuilder;
import io.airlift.airline.Command;
import io.airlift.airline.Help;
import io.airlift.airline.Option;
import io.airlift.airline.OptionType;
import io.airlift.airline.ParseException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import org.apache.brooklyn.api.catalog.BrooklynCatalog;
import org.apache.brooklyn.cli.Main.LaunchCommand;
import org.apache.brooklyn.core.BrooklynVersion;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
import org.apache.brooklyn.util.exceptions.FatalRuntimeException;
import org.apache.brooklyn.util.exceptions.UserFacingException;
import org.apache.brooklyn.util.text.KeyValueParser;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
/**
* This class is the primary CLI for brooklyn.
* Run with the `help` argument for help.
*
* This class is designed for subclassing, with subclasses typically:
*
providing their own static {@link #main(String...)} (of course) which need simply invoke
* {@link #execCli(String[])} with the arguments
* returning their CLI name (e.g. "start.sh") in an overridden {@link #cliScriptName()}
* providing an overridden {@link LaunchCommand} via {@link #cliLaunchCommand()} if desired
* providing any other CLI customisations by overriding {@link #cliBuilder()}
* (typically calling the parent and then customizing the builder)
* populating a custom catalog using {@link LaunchCommand#populateCatalog(BrooklynCatalog)}
*/
public abstract class AbstractMain {
private static final Logger log = LoggerFactory.getLogger(AbstractMain.class);
// Launch banner
public static final String DEFAULT_BANNER =
" _ _ _ \n" +
"| |__ _ __ ___ ___ | | _| |_ _ _ __ (R)\n" +
"| '_ \\| '__/ _ \\ / _ \\| |/ / | | | | '_ \\ \n" +
"| |_) | | | (_) | (_) | <| | |_| | | | |\n" +
"|_.__/|_| \\___/ \\___/|_|\\_\\_|\\__, |_| |_|\n" +
" |___/ "+BrooklynVersion.get()+"\n";
// Error codes
public static final int SUCCESS = 0;
public static final int PARSE_ERROR = 1;
public static final int EXECUTION_ERROR = 2;
public static final int CONFIGURATION_ERROR = 3;
/**
* Field intended for sub-classes (with their own {@code main()}) to customize the banner.
* All accesses to the banner are done through this field, to ensure consistent customization.
*
* Note that a {@code getBanner()} method is not an option for supporting this, because
* it is accessed from static inner-classes (such as {@link InfoCommand}, so non-static
* methods are not an option (and one can't override static methods).
*/
protected static volatile String banner = DEFAULT_BANNER;
/** abstract superclass for commands defining global options, but not arguments,
* as that prevents Help from being injectable in the {@link HelpCommand} subclass */
public static abstract class BrooklynCommand implements Callable {
@Option(type = OptionType.GLOBAL, name = { "-v", "--verbose" }, description = "Verbose mode")
public boolean verbose;
@Option(type = OptionType.GLOBAL, name = { "-q", "--quiet" }, description = "Quiet mode")
public boolean quiet;
@VisibleForTesting
protected PrintStream stdout = System.out;
@VisibleForTesting
protected PrintStream stderr = System.err;
@VisibleForTesting
protected InputStream stdin = System.in;
public ToStringHelper string() {
return Objects.toStringHelper(getClass())
.add("verbose", verbose)
.add("quiet", quiet);
}
@Override
public String toString() {
return string().toString();
}
}
/** common superclass for commands, defining global options (in our super) and extracting the arguments */
public static abstract class BrooklynCommandCollectingArgs extends BrooklynCommand {
/** extra arguments */
@Arguments
public List arguments = new ArrayList();
/** @return true iff there are arguments; it also sys.errs a warning in that case */
protected boolean warnIfArguments() {
if (arguments.isEmpty()) return false;
stderr.println("Invalid subcommand arguments: "+Strings.join(arguments, " "));
return true;
}
/** throw {@link ParseException} iff there are arguments */
protected void failIfArguments() {
if (arguments.isEmpty()) return ;
throw new ParseException("Invalid subcommand arguments '"+Strings.join(arguments, " ")+"'");
}
@Override
public ToStringHelper string() {
return super.string()
.add("arguments", arguments);
}
}
/** superclass which reads `-D` system property definitions and applies them
*
* useful when scripting, e.g. where brooklyn.sh encodes `java o.a.b.Main "$@"`
* but we want the caller to be able to pass system properties
*/
public static abstract class BrooklynCommandWithSystemDefines extends BrooklynCommandCollectingArgs {
@Option(type = OptionType.GLOBAL, name = { "-D" }, description = "Set java system property")
public List defines1;
@Option(name = { "-D" }, description = "Set java system property")
public List defines2;
public List getDefines() { return MutableList.copyOf(defines1).appendAll(defines2); }
public Map getDefinesAsMap() { return KeyValueParser.parseMap(Strings.join(getDefines(),",")); }
public void applyDefinesAsSystemProperties() { System.getProperties().putAll(getDefinesAsMap()); }
@Override
public ToStringHelper string() {
return super.string()
.add("defines", getDefines());
}
@Override
public Void call() throws Exception {
applyDefinesAsSystemProperties();
return null;
}
}
@Command(name = "help", description = "Display help for available commands")
public static class HelpCommand extends BrooklynCommand {
@Inject
public Help help;
@Override
public Void call() throws Exception {
if (log.isDebugEnabled()) log.debug("Invoked help command: {}", this);
return help.call();
}
}
@Command(name = "info", description = "Display information about brooklyn")
public static class InfoCommand extends BrooklynCommandCollectingArgs {
@Override
public Void call() throws Exception {
if (log.isDebugEnabled()) log.debug("Invoked info command: {}", this);
warnIfArguments();
System.out.println(banner);
System.out.println("Version: " + BrooklynVersion.get());
if (BrooklynVersion.INSTANCE.isSnapshot()) {
System.out.println("Git SHA1: " + BrooklynVersion.INSTANCE.getSha1FromOsgiManifest());
}
System.out.println("Website: http://brooklyn.apache.org");
System.out.println("Source: https://github.com/apache/brooklyn");
System.out.println();
System.out.println("Copyright 2011-2016 The Apache Software Foundation.");
System.out.println("Licensed under the Apache 2.0 License");
System.out.println();
return null;
}
}
public static class DefaultInfoCommand extends InfoCommand {
@Override
public Void call() throws Exception {
super.call();
System.out.println("ERROR: No command specified.");
System.out.println();
throw new ParseException("No command specified.");
}
}
/** method intended for overriding when the script filename is different
* @return the name of the script the user has invoked */
protected String cliScriptName() {
return "brooklyn";
}
/**
* Build the commands.
*/
protected abstract CliBuilder cliBuilder();
protected void execCli(String ...args) {
execCli(cliBuilder().build(), args);
}
protected void execCli(Cli parser, String ...args) {
try {
log.debug("Parsing command line arguments: {}", Arrays.asList(args));
BrooklynCommand command = parser.parse(args);
log.debug("Executing command: {}", command);
command.call();
System.exit(SUCCESS);
} catch (ParseException pe) { // looks like the user typed it wrong
System.err.println("Parse error: " + pe.getMessage()); // display
// error
System.err.println(getUsageInfo(parser)); // display cli help
System.exit(PARSE_ERROR);
} catch (FatalConfigurationRuntimeException e) {
log.error("Configuration error: "+e.getMessage(), e.getCause());
System.err.println("Configuration error: " + e.getMessage());
System.exit(CONFIGURATION_ERROR);
} catch (FatalRuntimeException e) { // anticipated non-configuration error
log.error("Startup error: "+e.getMessage(), e.getCause());
System.err.println("Startup error: "+e.getMessage());
System.exit(EXECUTION_ERROR);
} catch (Exception e) { // unexpected error during command execution
log.error("Execution error: " + e.getMessage(), e);
System.err.println("Execution error: " + e.getMessage());
if (!(e instanceof UserFacingException))
e.printStackTrace();
System.exit(EXECUTION_ERROR);
}
}
protected String getUsageInfo(Cli parser) {
StringBuilder help = new StringBuilder();
help.append("\n");
Help.help(parser.getMetadata(), Collections.emptyList(), help);
return help.toString();
}
}