/*******************************************************************************
* Copyright (c) 2010-2011 VIVO Harvester Team. For full list of contributors, please see the AUTHORS file provided.
* All rights reserved.
* This program and the accompanying materials are made available under the terms of the new BSD license which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.html
******************************************************************************/
package org.vivoweb.harvester.util.args;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vivoweb.harvester.util.FileAide;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Parsed arguments from commandline and config files
* @author Christopher Haines (hainesc@ctrip.ufl.edu)
* @author Nicholas Skaggs (nskaggs@ctrip.ufl.edu)
*/
public class ArgList {
/**
* SLF4J Logger
*/
private static Logger log = LoggerFactory.getLogger(ArgList.class);
/**
* Argument set from commandline arguments
*/
private CommandLine oCmdSet;
/**
* Map from configuration file
*/
private Map<String, List<String>> confMap;
/**
* Argument Parse Mappings
*/
private ArgParser argParser;
/**
* Constructor
* @param p parser
* @param args commandline args
* @throws IOException error parsing args
* @throws UsageException user requested usage message
*/
protected ArgList(ArgParser p, String[] args) throws IOException, UsageException {
this(p, args, true);
}
/**
* Constructor
* @param p parser
* @param args commandline args
* @param logLines log lines
* @throws IOException error parsing args
* @throws UsageException user requested usage message
*/
protected ArgList(ArgParser p, String[] args, boolean logLines) throws IOException, UsageException {
try {
this.argParser = p;
if(logLines) {
log.debug("running " + p.getAppName());
log.debug("command line args: " + getSanitizedArgString(args));
}
this.oCmdSet = new PosixParser().parse(this.argParser.getOptions(), args);
if(this.oCmdSet.hasOption("help")) {
throw new UsageException();
}
String[] confArgs = {""};
if(this.oCmdSet.hasOption("X")) {
this.confMap = new ConfigParser().parseConfig(this.oCmdSet.getOptionValue("X"));
confArgs = new ConfigParser().configToArgs(this.oCmdSet.getOptionValue("X"));
if(logLines) {
log.debug("config file args: " + getSanitizedArgString(confArgs));
}
} else {
this.confMap = null;
}
for(ArgDef arg : this.argParser.getArgDefs()) {
if(arg.isRequired()) {
String argName;
if(arg.getShortOption() != null) {
argName = arg.getShortOption().toString();
} else {
argName = arg.getLongOption();
}
if(!has(argName)) {
throw new IllegalArgumentException("Missing Argument: " + argName);
}
}
}
} catch(ParseException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Get the values for the given argdef
* @param argdef the argdef
* @return the values
*/
private List<String> getConfArgValues(ArgDef argdef) {
List<String> retVal = new ArrayList<String>();
Character shortOp = argdef.getShortOption();
if((shortOp != null) && this.confMap.containsKey(shortOp.toString())) {
retVal.addAll(this.confMap.get(shortOp.toString()));
}
String longOp = argdef.getLongOption();
if((longOp != null) && this.confMap.containsKey(longOp)) {
retVal.addAll(this.confMap.get(longOp));
}
if(retVal.isEmpty() || retVal.size() < 1) {
return null;
}
return retVal;
}
/**
* Get the sanitized string of args
* @param args the args
* @return the sanitized string
*/
private String getSanitizedArgString(String[] args) {
StringBuilder sb = new StringBuilder();
String s;
Set<String> filters = new HashSet<String>();
filters.add("--inputUsername");
filters.add("--inputPassword");
filters.add("--outputUsername");
filters.add("--outputPassword");
filters.add("--username");
filters.add("--password");
filters.add("-.*dbUser");
filters.add("-.*dbPass");
for(int x = 0; x < args.length; x++) {
if(x != 0) {
sb.append(" ");
}
s = args[x];
sb.append(s);
for(String regex : filters) {
if(s.matches(regex)) {
sb.append(" ******");
x++;
break;
}
}
}
return sb.toString();
}
/**
* Gets the value of the argument or default value if not set, null if no default value
* @param arg argument to get
* @return the value
*/
public String get(String arg) {
ArgDef argdef = this.argParser.getOptMap().get(arg);
if(argdef == null) {
throw new IllegalArgumentException("No such parameter: "+arg);
}
if(!argdef.hasParameter()) {
throw new IllegalArgumentException(arg + " has no parameters");
}
if(argdef.hasParameters()) {
throw new IllegalArgumentException(arg + " potentially has more than one value, use getAll()");
}
if(argdef.isParameterValueMap()) {
throw new IllegalArgumentException(arg + " is a value map parameter, use getValueMap()");
}
String retVal = null;
if(this.oCmdSet.hasOption(arg)) {
retVal = this.oCmdSet.getOptionValue(arg);
} else {
List<String> confVals = null;
if((this.confMap != null) && ((confVals = getConfArgValues(argdef)) != null)) {
if(confVals.size() > 1) {
throw new IllegalArgumentException("Invalid config file: contains more than one value for parameter '"+argdef.getOptionString()+"'");
}
retVal = confVals.get(0);
} else {
retVal = this.argParser.getOptMap().get(arg).getDefaultValue();
}
}
if(retVal != null) {
retVal = retVal.trim();
}
return retVal;
}
/**
* Gets the value map for the argument
* @param arg argument to get
* @return the value map
*/
public Map<String, String> getValueMap(String arg) {
ArgDef argdef = this.argParser.getOptMap().get(arg);
if(argdef == null) {
throw new IllegalArgumentException("No such parameter: "+arg);
}
if(!argdef.hasParameter()) {
throw new IllegalArgumentException(arg + " has no parameters");
}
if(!argdef.isParameterValueMap()) {
if(argdef.hasParameters()) {
throw new IllegalArgumentException(arg + " is not a value map parameter, use getAll()");
}
throw new IllegalArgumentException(arg + " is not a value map parameter, use get()");
}
Map<String, String> p = new HashMap<String, String>();
List<String> confVals = null;
if((this.confMap != null) && ((confVals = getConfArgValues(argdef)) != null)) {
for(String confVal : confVals) {
String[] confValSplit = confVal.split("=", 2);
if(confValSplit.length != 2) {
throw new IllegalArgumentException("Invalid config file: contains non-value map (paramName=paramValue) value for parameter '"+argdef.getOptionString()+"'");
}
p.put(confValSplit[0].trim(), confValSplit[1].trim());
}
}
Properties props = this.oCmdSet.getOptionProperties(arg);
for(String prop : props.stringPropertyNames()) {
p.put(prop.trim(), props.getProperty(prop).trim());
}
return p;
}
/**
* Gets the values of the argument (excluding a default value)
* @param arg argument to get
* @return the values
*/
public List<String> getAll(String arg) {
return getAll(arg, false);
}
/**
* Gets the values of the argument
* @param arg argument to get
* @param includeDefaultValue should we include a default value if it exists?
* @return the values
*/
public List<String> getAll(String arg, boolean includeDefaultValue) {
ArgDef argdef = this.argParser.getOptMap().get(arg);
if(argdef == null) {
throw new IllegalArgumentException("No such parameter: "+arg);
}
if(!argdef.hasParameter()) {
throw new IllegalArgumentException(arg + " has no parameters");
}
if(!argdef.hasParameters()) {
if(argdef.isParameterValueMap()) {
throw new IllegalArgumentException(arg + " is a value map parameter, use getValueMap()");
}
throw new IllegalArgumentException(arg + " has only one parameter, use get()");
}
List<String> retVal = new LinkedList<String>();
if(this.oCmdSet.hasOption(arg)) {
for(String value : this.oCmdSet.getOptionValues(arg)) {
retVal.add(value.trim());
}
}
List<String> confVals = null;
if((this.confMap != null) && ((confVals = getConfArgValues(argdef)) != null)) {
for(String confVal : confVals) {
retVal.add(confVal.trim());
}
}
if((includeDefaultValue || retVal.isEmpty()) && argdef.hasDefaultValue()) {
retVal.add(argdef.getDefaultValue().trim());
}
return retVal;
}
/**
* Determines if the given argument has been set or has a default value
* @param arg the argument
* @return true if a value has been set (from any of command line, config files, or default value)
*/
public boolean has(String arg) {
ArgDef argdef = this.argParser.getOptMap().get(arg);
if(argdef == null) {
throw new IllegalArgumentException("No such parameter: "+arg);
}
if(this.oCmdSet.hasOption(arg)) {
return true;
} else if((this.confMap != null) && (getConfArgValues(argdef) != null)) {
return true;
} else if(argdef.hasDefaultValue()) {
return true;
}
return false;
}
/**
* Config Parser for Tasks
* @author Christopher Haines (hainesc@ctrip.ufl.edu)
*/
private class ConfigParser extends DefaultHandler {
/**
* The param list from config file
*/
private Map<String, List<String>> params;
/**
* Temporary container for cdata
*/
private String tempVal;
/**
* Temporary container for parameter id
*/
private String tempParamName;
/**
* Default Constructor
*/
protected ConfigParser() {
this.params = new HashMap<String, List<String>>();
this.tempVal = "";
}
/**
* Converts the contents of a config file to commandline arguments
* @param filePath path to the config file
* @return equivalent commandline argument array
* @throws IOException error reading config file
*/
String[] configToArgs(String filePath) throws IOException {
Map<String, List<String>> parameters = parseConfig(filePath);
List<String> paramList = new LinkedList<String>();
for(String key : parameters.keySet()) {
for(String value : parameters.get(key)) {
if(!value.equalsIgnoreCase("false")) {
paramList.add("--" + key);
if(!value.equalsIgnoreCase("true")) {
paramList.add(value);
}
}
}
}
return paramList.toArray(new String[]{});
}
/**
* Parses a configuration file describing a Task
* @param filename the name of the file to parse
* @return the Task described by the config file
* @throws IOException xml parsing error
*/
Map<String, List<String>> parseConfig(String filename) throws IOException {
// get a factory
SAXParserFactory spf = SAXParserFactory.newInstance();
try {
// get a new instance of parser
SAXParser sp = spf.newSAXParser();
// parse the file and also register this class for call backs
sp.parse(FileAide.getInputStream(filename), this);
} catch(ParserConfigurationException e) {
throw new IOException(e);
} catch(SAXException e) {
throw new IOException(e);
}
return this.params;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
this.tempVal = "";
this.tempParamName = "";
if(qName.equalsIgnoreCase("Task") || qName.equalsIgnoreCase("Config")) {
// Do nothing, but keep to prevent falling into else
} else if(qName.equalsIgnoreCase("Param")) {
this.tempParamName = attributes.getValue("name");
} else {
throw new SAXException("Unknown Tag: " + qName);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
this.tempVal += new String(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(qName.equalsIgnoreCase("Task") || qName.equalsIgnoreCase("Config")) {
// Do nothing, but leave it here so it doesn't fall into else statement
} else if(qName.equalsIgnoreCase("Param")) {
if(!this.params.containsKey(this.tempParamName)) {
this.params.put(this.tempParamName, new LinkedList<String>());
}
this.params.get(this.tempParamName).add(this.tempVal);
} else {
throw new SAXException("Unknown Tag: " + qName);
}
}
}
}