/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;
import java.io.Console;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.commons_voltpatches.cli.CommandLine;
import org.apache.commons_voltpatches.cli.CommandLineParser;
import org.apache.commons_voltpatches.cli.HelpFormatter;
import org.apache.commons_voltpatches.cli.Options;
import org.apache.commons_voltpatches.cli.PosixParser;
import javax.net.ssl.SSLContext;
public abstract class CLIConfig {
@Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection.
@Target({ElementType.FIELD}) // This annotation can only be applied to class methods.
public @interface Option {
String opt() default "";
String shortOpt() default "";
boolean hasArg() default true;
boolean required() default false;
String desc() default "";
}
@Retention(RetentionPolicy.RUNTIME) // Make this annotation accessible at runtime via reflection.
@Target({ElementType.FIELD}) // This annotation can only be applied to class methods.
public @interface AdditionalArgs {
String opt() default "";
boolean hasArg() default false;
boolean required() default false;
String desc() default "";
}
// Apache Commons CLI API - requires JAR
protected final Options options = new Options();
protected Options helpmsgs = new Options();
protected String cmdName = "command";
protected String configDump;
protected String usage;
protected SSLContext m_sslContext;
public void setSSLContext(SSLContext sslContext) {
m_sslContext = sslContext;
}
public SSLContext getSSLContext() {
return m_sslContext;
}
public void exitWithMessageAndUsage(String msg) {
System.err.println(msg);
printUsage();
System.exit(-1);
}
public void printUsage() {
// automatically generate the help statement
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(cmdName, helpmsgs, false);
}
private void assignValueToField(Field field, String value) throws Exception {
if ((value == null) || (value.length() == 0)) {
return;
}
field.setAccessible(true);
Class<?> cls = field.getType();
if ((cls == boolean.class) || (cls == Boolean.class)) {
field.set(this, Boolean.parseBoolean(value));
} else if ((cls == byte.class) || (cls == Byte.class)) {
field.set(this, Byte.parseByte(value));
} else if ((cls == short.class) || (cls == Short.class)) {
field.set(this, Short.parseShort(value));
} else if ((cls == int.class) || (cls == Integer.class)) {
field.set(this, Integer.parseInt(value));
} else if ((cls == long.class) || (cls == Long.class)) {
field.set(this, Long.parseLong(value));
} else if ((cls == float.class) || (cls == Float.class)) {
field.set(this, Float.parseFloat(value));
} else if ((cls == double.class) || (cls == Double.class)) {
field.set(this, Double.parseDouble(value));
} else if ((cls == String.class)) {
field.set(this, value);
} else if (value.length() == 1 && ((cls == char.class) || (cls == Character.class))) {
field.set(this, value.charAt(0));
} else {
System.err.println("Parsing failed. Reason: can not assign " + value + " to "
+ cls.toString() + " class");
printUsage();
System.exit(-1);
}
}
public void parse(String cmdName, String[] args) {
this.cmdName = cmdName;
try {
options.addOption("help","h", false, "Print this message");
// add all of the declared options to the cli
for (Field field : getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Option.class)) {
Option option = field.getAnnotation(Option.class);
String opt = option.opt();
if ((opt == null) || (opt.trim().length() == 0)) {
opt = field.getName();
}
String shortopt = option.shortOpt();
if ((shortopt == null) || (shortopt.trim().length() == 0)) {
options.addOption(null, opt, option.hasArg(), option.desc());
helpmsgs.addOption(null, opt, option.hasArg(), option.desc());
} else {
options.addOption(shortopt, opt, option.hasArg(), option.desc());
helpmsgs.addOption(shortopt, opt, option.hasArg(), option.desc());
}
} else if (field.isAnnotationPresent(AdditionalArgs.class)) {
AdditionalArgs params = field.getAnnotation(AdditionalArgs.class);
String opt = params.opt();
if ((opt == null) || (opt.trim().length() == 0)) {
opt = field.getName();
}
options.addOption(opt, params.hasArg(), params.desc());
}
}
CommandLineParser parser = new PosixParser();
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("help")) {
printUsage();
System.exit(0);
}
String[] leftargs = cmd.getArgs();
int leftover = 0;
// string key-value pairs
Map<String, String> kvMap = new TreeMap<String, String>();
for (Field field : getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Option.class) ) {
Option option = field.getAnnotation(Option.class);
String opt = option.opt();
if ((opt == null) || (opt.trim().length() == 0)) {
opt = field.getName();
}
if (cmd.hasOption(opt)) {
if (option.hasArg()) {
assignValueToField(field, cmd.getOptionValue(opt));
}
else {
if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {
field.setAccessible(true);
try {
field.set(this, true);
} catch (Exception e) {
throw new IllegalArgumentException (e);
}
}
else {
printUsage();
}
}
}
else {
if (option.required()) {
printUsage();
}
}
field.setAccessible(true);
kvMap.put(opt, field.get(this).toString());
} else if (field.isAnnotationPresent(AdditionalArgs.class)) {
// Deal with --table=BLHA, offer nice error message later
leftover++;
}
}
if (leftargs != null) {
if (leftargs.length <= leftover) {
Field[] fields = getClass().getDeclaredFields();
for (int i = 0,j=0; i<leftargs.length; i++) {
for (;j < fields.length; j++) {
if (fields[j].isAnnotationPresent(AdditionalArgs.class)) {
break;
}
}
fields[j].setAccessible(true);
fields[j].set(this, leftargs[i]);
}
} else {
System.err.println("Expected " + leftover + " args, but receive " + leftargs.length + " args");
printUsage();
System.exit(-1);
}
}
// check that the values read are valid
// this code is specific to your app
validate();
// build a debug string
StringBuilder sb = new StringBuilder();
for (Entry<String, String> e : kvMap.entrySet()) {
sb.append(e.getKey()).append(" = ").append(e.getValue()).append("\n");
}
configDump = sb.toString();
}
catch (Exception e) {
System.err.println("Parsing failed. Reason: " + e.getMessage());
printUsage();
System.exit(-1);
}
}
public static String readPasswordIfNeeded(String user, String pwd, String prompt) throws IOException
{
// If a username was specified, and no password was specified,
// then read password from the console
if ((null!=user) && (user.trim().length()>0))
{
if ((pwd==null) || (pwd.trim().length() == 0))
{
Console console = System.console();
if (console == null) {
throw new IOException("Unable to read password from console.");
}
char[] val = console.readPassword("%s", prompt);
if (val != null)
{
return new String(val);
}
else
{
// Allow the user to omit the password, let the authentication
// logic handle password validation
return "";
}
}
}
return pwd;
}
public void validate() {}
public String getConfigDumpString() {
return configDump;
}
}