org.wildfly.swarm.cli.CommandLine Maven / Gradle / Ivy
/**
* Copyright 2015-2017 Red Hat, Inc, and individual contributors.
*
* 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.wildfly.swarm.cli;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.enterprise.inject.Vetoed;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.wildfly.swarm.Swarm;
import org.wildfly.swarm.SwarmInfo;
import org.wildfly.swarm.spi.api.SwarmProperties;
/**
* A parsed command-line.
*
* @author Bob McWhirter
*/
@Vetoed
public class CommandLine {
private static final String CONFIG_ELEMENT = "";
private static final String FRACTION = "fraction";
private static final String ALL = "all";
/**
* Default option for parsing -h and --help
*/
public static final Option HELP = new Option()
.withLong("help")
.withShort('h')
.withDescription("Display this help")
.withDefault(() -> false)
.then((cmd, opt, value) -> cmd.put(opt, true));
public static final Option CONFIG_HELP = new Option()
.withLong("config-help")
.hasValue("")
.withDescription("Display configuration help by fraction, or 'all' for all")
.then((cmd, opt, value) -> cmd.put(opt, value));
public static final Option YAML_HELP = new Option()
.withLong("yaml-help")
.hasValue("")
.withDescription("Display example YAML configuration by fraction, or 'all' for all")
.then((cmd, opt, value) -> cmd.put(opt, value));
/**
* Default option for parsing -v and --version
*/
public static final Option VERSION = new Option()
.withLong("version")
.withShort('v')
.withDescription("Display the version of WildFly Swarm")
.withDefault(() -> false)
.then((cmd, opt, value) -> cmd.put(opt, true));
/**
* Default option for parsing -Dname and -Dname=value
*/
public static final Option PROPERTY = new Option()
.withShort('D')
.hasValue("[=]")
.valueMayBeSeparate(false)
.withDescription("Set a system property")
.withDefault(Properties::new)
.then((cmd, opt, value) -> {
String[] nameValue = value.split("=");
Properties props = cmd.get(opt);
String propName = nameValue[0];
String propValue = "true";
if (nameValue.length > 1) {
propValue = nameValue[1];
}
props.setProperty(propName, propValue);
});
/**
* Default option for parsing -P
*/
public static final Option PROPERTIES_URL = new Option()
.withShort('P')
.withLong("properties")
.hasValue("")
.withDescription("Load system properties from the given URL")
.then((cmd, opt, value) -> cmd.put(opt, Option.toURL(value)));
/**
* Default option for parsing -c and --server-config
*/
public static final Option SERVER_CONFIG = new Option()
.withShort('c')
.withLong("server-config")
.hasValue(CONFIG_ELEMENT)
.valueMayBeSeparate(true)
.withDescription("URL of the server configuration (e.g. standalone.xml)")
.withDefault(() -> resolveResource("standalone.xml"))
.then((cmd, opt, value) -> cmd.put(opt, Option.toURL(value)));
public static final Option> CONFIG = new Option>()
.withShort('s')
.withLong("config")
.hasValue("")
.valueMayBeSeparate(true)
.withDescription("URL to configuration YAML to use")
.then((cmd, opt, value) -> {
List configs = cmd.get(opt);
if (configs == null) {
configs = new ArrayList<>();
cmd.put(opt, configs);
}
configs.add(Option.toURL(value));
});
public static final Option> PROFILES = new Option>()
.withShort('S')
.withLong("profile")
.hasValue("")
.valueMayBeSeparate(true)
.withDescription("Selected profiles")
.then((cmd, opt, value) -> {
List profiles = cmd.get(opt);
if (profiles == null) {
profiles = new ArrayList<>();
cmd.put(opt, profiles);
}
profiles.add(value);
});
/**
* Default option for parsing -b
*/
public static final Option BIND = new Option()
.withShort('b')
.hasValue("")
.valueMayBeSeparate(true)
.withDescription("Set the property " + SwarmProperties.BIND_ADDRESS + " to ")
.then(CommandLine::put);
/**
* Default set of options
*/
public static Options defaultOptions() {
return new Options(
HELP,
CONFIG_HELP,
YAML_HELP,
VERSION,
PROPERTY,
PROPERTIES_URL,
SERVER_CONFIG,
CONFIG,
PROFILES,
BIND
);
}
CommandLine(Options options) {
this.options = options;
}
/**
* Put a value under a given key.
*
* @param key The key.
* @param value The value.
* @param The type of the value.
*/
public void put(Option key, T value) {
this.values.put(key, value);
}
/**
* Retrieve a value under a given key.
*
* @param key The key.
* @param The type of the value.
* @return The previously stored value, or the default provided by key if none has been previously stored. The default will then be stored.
*/
@SuppressWarnings("unchecked")
public T get(Option key) {
T v = (T) this.values.get(key);
if (v == null) {
v = key.defaultValue();
this.values.put(key, v);
}
return v;
}
/**
* Display help for the options associated with the creation of this command-line.
*
* @param out The output stream to display help upon.
*/
public void displayHelp(PrintStream out) {
this.options.displayHelp(out);
}
public void displayConfigHelp(PrintStream out, String fraction) throws IOException, ModuleLoadException {
ModuleClassLoader cl = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("swarm.application")).getClassLoader();
Enumeration docs = cl.getResources("META-INF/configuration-meta.properties");
Properties props = new Properties();
while (docs.hasMoreElements()) {
URL each = docs.nextElement();
Properties fractionDocs = new Properties();
fractionDocs.load(each.openStream());
if (fraction.equals(ALL) || fraction.equals(fractionDocs.getProperty(FRACTION))) {
fractionDocs.remove(FRACTION);
props.putAll(fractionDocs);
}
}
props.stringPropertyNames().stream()
.sorted()
.forEach(key -> {
out.println("# " + key);
out.println();
out.println(formatDocs(" ", props.getProperty(key)));
out.println();
});
}
public void dumpYaml(PrintStream out, String fraction) throws IOException, ModuleLoadException {
ModuleClassLoader cl = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("swarm.application")).getClassLoader();
Enumeration docs = cl.getResources("META-INF/configuration-meta.properties");
Properties props = new Properties();
while (docs.hasMoreElements()) {
URL each = docs.nextElement();
Properties fractionDocs = new Properties();
fractionDocs.load(each.openStream());
if (fraction.equals(ALL) || fraction.equals(fractionDocs.getProperty(FRACTION))) {
fractionDocs.remove(FRACTION);
props.putAll(fractionDocs);
}
}
YamlDumper.dump(out, props);
}
private String formatDocs(String indent, String docs) {
StringTokenizer tokens = new StringTokenizer(docs);
StringBuilder formatted = new StringBuilder();
int lineLength = indent.length();
boolean freshLine = true;
formatted.append(indent);
while (tokens.hasMoreElements()) {
String next = tokens.nextToken();
if ((lineLength + 1 + next.length()) > 80) {
formatted.append("\n");
formatted.append(indent);
lineLength = indent.length();
freshLine = true;
}
if (freshLine) {
freshLine = false;
} else {
formatted.append(" ");
}
lineLength += next.length();
formatted.append(next);
}
return formatted.toString();
}
/**
* Display the version.
*
* @param out The output stream to display help upon.
*/
public void displayVersion(PrintStream out) {
out.println("WildFly Swarm version " + SwarmInfo.VERSION);
}
/**
* Apply properties to the system properties.
*
* Applies values stored through the Key.PROPERTIES
,
* Key.PROPERTIES_URL
or Key.BIND
options.
*
* @throws IOException If a URL is attempted to be read and fails.
*/
public void applyProperties(Swarm swarm) throws IOException {
URL propsUrl = get(PROPERTIES_URL);
if (propsUrl != null) {
Properties urlProps = new Properties();
urlProps.load(propsUrl.openStream());
for (String name : urlProps.stringPropertyNames()) {
swarm.withProperty(name, urlProps.getProperty(name));
}
}
Properties props = get(PROPERTY);
for (String name : props.stringPropertyNames()) {
swarm.withProperty(name, props.getProperty(name));
}
if (get(BIND) != null) {
swarm.withProperty(SwarmProperties.BIND_ADDRESS, get(BIND));
}
}
/**
* Apply configuration to the container.
*
*
Applies configuration from Key.SERVER_CONFIG
and Key.STAGE_CONFIG
.
*
* @param swarm Swarm instance to configure.
* @throws MalformedURLException If a URL is attempted to be read and fails.
*/
public void applyConfigurations(Swarm swarm) throws IOException {
if (get(SERVER_CONFIG) != null) {
swarm.withXmlConfig(get(SERVER_CONFIG));
}
if (get(CONFIG) != null) {
List configs = get(CONFIG);
for (URL config : configs) {
swarm.withConfig(config);
}
}
if (get(PROFILES) != null) {
List profiles = get(PROFILES);
for (String profile : profiles) {
swarm.withProfile(profile);
}
}
}
/**
* Apply properties and configuration from the parsed commandline to a container.
*
* @param swarm The Swarm instance to apply configuration to.
* @throws IOException If an error occurs resolving any URL.
*/
public void apply(Swarm swarm) throws IOException, ModuleLoadException {
applyProperties(swarm);
applyConfigurations(swarm);
if (get(HELP)) {
displayVersion(System.err);
System.err.println();
displayHelp(System.err);
System.exit(0);
}
if (get(CONFIG_HELP) != null) {
displayConfigHelp(System.err, get(CONFIG_HELP));
System.exit(0);
}
if (get(YAML_HELP) != null) {
dumpYaml(System.err, get(YAML_HELP));
System.exit(0);
}
if (get(VERSION)) {
displayVersion(System.err);
}
}
void extraArgument(String arg) {
this.extraArguments.add(arg);
}
/**
* Any un-parsed non-option arguments.
*
* @return The list of unparsed arguments.
*/
public List extraArguments() {
return this.extraArguments;
}
/**
* Any un-parsed non-option arguments.
*
* @return The array of unparsed arguments.
*/
public String[] extraArgumentsArray() {
return this.extraArguments.toArray(new String[this.extraArguments.size()]);
}
void invalidArgument(String arg) {
this.invalidArguments.add(arg);
}
/**
* Any invalid options seen during parsing.
*
* @return The list of invalid arguments.
*/
public List invalidArguments() {
return this.invalidArguments;
}
/**
* Determine if any invalid arguments were seen during the parse.
*
* @return true
if {@link #invalidArguments()} is not empty, otherwise false
.
*/
public boolean hasInvalidArguments() {
return !this.invalidArguments.isEmpty();
}
/**
* Parse an array of arguments using the default options.
*
* @param args The args to parse.
* @return The parsed CommandLine
.
*/
public static CommandLine parse(String... args) throws Exception {
return CommandLineParser.parse(defaultOptions(), args);
}
/**
* Parse an array of arguments using specific options.
*
* @param options The options to use.
* @param args The args to parse.
* @return The parsed CommandLine
.
*/
public static CommandLine parse(Options options, String... args) throws Exception {
return CommandLineParser.parse(options, args);
}
private static URL resolveResource(String path) {
Path candidate = Paths.get(path);
if (Files.exists(candidate)) {
try {
return candidate.toUri().toURL();
} catch (MalformedURLException e) {
// ignore
}
}
URL yml = null;
try {
Module appModule = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("swarm.application"));
yml = appModule.getClassLoader().getResource(path);
if (yml != null) {
return yml;
}
} catch (ModuleLoadException e) {
// ignore;
}
yml = ClassLoader.getSystemClassLoader().getResource(path);
return yml;
}
private final Options options;
private final Map