/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package fedora.client.utility.validate.process; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import fedora.client.utility.validate.remote.ServiceInfo; import fedora.server.errors.QueryParseException; import fedora.server.search.Condition; import fedora.server.search.FieldSearchQuery; /** * Parse and store the command-line arguments for the {@link ValidatorProcess}. * * @author Jim Blake */ public class ValidatorProcessParameters { /** * A description of how to invoke the process. */ public static final String USAGE = "usage: ValidatorProcess -serverurl <url> -username <username> " + "-password <password> " + "{-terms <terms> | -query <query> | -pidfile <path>} " + "[-logConfig <filename>]"; /** * Required parameter: the base url of the Fedora repository server. E.g. * <code>http://localhost:8080/fedora</code> */ public static final String PARAMETER_SERVER_URL = "-serverurl"; /** * Required parameter: a user who is authorized to access the repository * objects. E.g. <code>fedoraAdmin</code> */ public static final String PARAMETER_USERNAME = "-username"; /** * Required parameter: the password for the user specified by '-username'. * E.g. <code>fedoraAdminPassword</code> */ public static final String PARAMETER_PASSWORD = "-password"; /** * Optional parameter: a "terms" string that can be passed to the * <code>findObjects</code> method in API-A. Supply any of -terms, -query, * or -pidfile, but not more than one. E.g. <code>demo*</code> */ public static final String PARAMETER_TERMS = "-terms"; /** * Optional parameter: a "query" string that can be passed to the * <code>findObjects</code> method in API-A. Supply any of -terms, -query, * or -pidfile, but not more than one. E.g. <code>state~A</code> */ public static final String PARAMETER_QUERY = "-query"; /** * Optional parameter: the path to a file containing a list of PIDs - these * are the objects to be validated - one PID per line, blank lines are * ignored, lines beginning with octothorpe ('#'), are comments. Supply any * of -terms, -query, or -pidfile, but not more than one. E.g. * <code>/usr/local/junk/pidfile.txt</code> */ public static final String PARAMETER_PIDFILE = "-pidfile"; /** * Optional parameter: the name of a Log4J properties-style configuration * file that will be used to restrict or format the output of the validator. * If this is not provided, output will be written to System.out. E.g. * <code>/usr/local/validator.config.properties</code> */ public static final String PARAMETER_LOG_CONFIG = "-logConfig"; public enum IteratorType { FS_QUERY, PIDFILE } private static final Set<String> ALL_PARAMETERS = Collections.unmodifiableSet(new HashSet<String>(Arrays .asList(PARAMETER_SERVER_URL, PARAMETER_USERNAME, PARAMETER_PASSWORD, PARAMETER_TERMS, PARAMETER_QUERY, PARAMETER_PIDFILE, PARAMETER_LOG_CONFIG))); /** * The URL, username and password of the repository we are operating * against. */ private final ServiceInfo serviceInfo; /** * The Log4J configuration properties obtained from the -logConfig file, or * an empty set of properties if no filename was provided. */ private final Properties logConfigProperties; /** * If the command-line arguments contain -terms or -query, this is set to * {@link IteratorType#FS_QUERY}. If we have a -pidfile instead, this is * set to {@link IteratorType#PIDFILE}. */ private final IteratorType iteratorType; /** * An API-A query object, built from either the -terms or -query parameter. */ private final FieldSearchQuery fieldSearchQuery; /** * A pidfile, from the -pidfile parameter. */ private final File pidfile; /** * Parse the command line arguments and check for validity. */ public ValidatorProcessParameters(String[] args) { Map<String, String> parms = parseArgsIntoMap(args); String username = getRequiredParameter(PARAMETER_USERNAME, parms); String password = getRequiredParameter(PARAMETER_PASSWORD, parms); URL serverUrl = getRequiredUrlParameter(PARAMETER_SERVER_URL, parms); serviceInfo = new ServiceInfo(serverUrl, username, password); String query = getOptionalParameter(PARAMETER_QUERY, parms); String terms = getOptionalParameter(PARAMETER_TERMS, parms); String pidfileParm = getOptionalParameter(PARAMETER_PIDFILE, parms); iteratorType = figureOutIteratorType(query, terms, pidfileParm); if (iteratorType == IteratorType.FS_QUERY) { fieldSearchQuery = assembleFieldSearchQuery(query, terms); pidfile = null; } else { fieldSearchQuery = null; pidfile = assemblePidfile(pidfileParm); } String logConfigFile = getOptionalParameter(PARAMETER_LOG_CONFIG, parms); logConfigProperties = readLogConfigFile(logConfigFile); } /** * Decide which of the command line arguments are keywords and which are * values, and build a map to hold them. */ private Map<String, String> parseArgsIntoMap(String[] args) { Map<String, String> parms = new HashMap<String, String>(); for (int i = 0; i < args.length; i++) { String key = args[i]; if (!isKeyword(key)) { throw new ValidatorProcessUsageException("'" + key + "' is not a keyword."); } if (!ALL_PARAMETERS.contains(key)) { throw new ValidatorProcessUsageException("'" + key + "' is not a recognized keyword."); } if (i >= args.length - 1) { parms.put(key, null); } else { String value = args[i + 1]; if (isKeyword(value)) { parms.put(key, null); } else { parms.put(key, value); i++; } } } return parms; } private boolean isKeyword(String arg) { return arg.startsWith("-"); } /** * Get the requested parameter from the map. Complain if it's not found or * has a null value. */ private String getRequiredParameter(String keyword, Map<String, String> parms) { if (!parms.containsKey(keyword)) { throw new ValidatorProcessUsageException("Parameter '" + keyword + "' is required."); } String value = parms.get(keyword); if (value == null) { throw new ValidatorProcessUsageException("Parameter '" + keyword + "' requires a value."); } else { return value; } } /** * Get the requested parameter from the map. Complain if not found, or not a * valid URL. */ private URL getRequiredUrlParameter(String keyword, Map<String, String> parms) { String urlString = getRequiredParameter(keyword, parms); try { return new URL(urlString); } catch (MalformedURLException e) { throw new ValidatorProcessUsageException("Value '" + urlString + "' for parameter '" + keyword + "' is not a valid URL: " + e.getMessage()); } } /** * Get the requested parameter from the map. If it's not there, return null, * but if it's there with no value, complain. */ private String getOptionalParameter(String keyword, Map<String, String> parms) { if (!parms.containsKey(keyword)) { return null; } String value = parms.get(keyword); if (value == null) { throw new ValidatorProcessUsageException("If parameter '" + keyword + "' is provided, it must have a value."); } else { return value; } } /** * Look at the parameters. Is this a query-based request or a pidfile-based * request? If we put in too many parms, or not enough, that's a problem. */ private IteratorType figureOutIteratorType(String query, String terms, String pidfileParm) { int howMany = (query == null ? 0 : 1) + (terms == null ? 0 : 1) + (pidfileParm == null ? 0 : 1); if (howMany == 0) { throw new ValidatorProcessUsageException("You must provide " + "either '" + PARAMETER_QUERY + "', '" + PARAMETER_TERMS + "' or '" + PARAMETER_PIDFILE + "'."); } if (howMany > 1) { throw new ValidatorProcessUsageException("You must provide only " + "one of these parameters: '" + PARAMETER_QUERY + "', '" + PARAMETER_TERMS + "' or '" + PARAMETER_PIDFILE + "'."); } return pidfileParm == null ? IteratorType.FS_QUERY : IteratorType.PIDFILE; } /** * A {@link FieldSearchQuery} may be made from a terms string or a query * string, but not both. */ private FieldSearchQuery assembleFieldSearchQuery(String query, String terms) { if (terms != null) { return new FieldSearchQuery(terms); } else { try { return new FieldSearchQuery(Condition.getConditions(query)); } catch (QueryParseException e) { throw new ValidatorProcessUsageException("Value '" + query + "' of parameter '" + PARAMETER_QUERY + "' is not a valid query string."); } } } /** * Is there a valid file out there for this parm? We already know that the * parms is not null. */ private File assemblePidfile(String pidfileParm) { File pidfile = new File(pidfileParm); if (!pidfile.exists()) { throw new ValidatorProcessUsageException("-pidfile does not exist: '" + pidfileParm + "'"); } if (!pidfile.canRead()) { throw new ValidatorProcessUsageException("-pidfile is not readable: '" + pidfileParm + "'"); } return pidfile; } /** * Try to read the log configuration properties from the supplied file. If * no file was specified, return an empty {@link Properties} object. */ private Properties readLogConfigFile(String logConfigFilename) { Properties props = new Properties(); if (logConfigFilename != null) { File propertiesFile = new File(logConfigFilename); if (!propertiesFile.exists()) { throw new ValidatorProcessUsageException(PARAMETER_LOG_CONFIG + " file '" + logConfigFilename + "' does not exist."); } if (!propertiesFile.isFile() || !propertiesFile.canRead()) { throw new ValidatorProcessUsageException(PARAMETER_LOG_CONFIG + " file '" + logConfigFilename + "' is not a readable file."); } try { props.load(new FileInputStream(propertiesFile)); } catch (IOException e) { throw new ValidatorProcessUsageException("Failed to load " + "properties from " + PARAMETER_LOG_CONFIG + " file '" + logConfigFilename + "'"); } } return props; } public ServiceInfo getServiceInfo() { return serviceInfo; } /** * Create a fresh {@link Properties} object to distribute, so this object * remains immutable. */ public Properties getLogConfigProperties() { Properties props = new Properties(); props.putAll(logConfigProperties); return props; } public IteratorType getIteratorType() { return iteratorType; } public FieldSearchQuery getQuery() { return fieldSearchQuery; } public File getPidfile() { return pidfile; } }