Author: [log in to unmask] Date: Wed Oct 29 18:59:03 2014 New Revision: 1335 Log: Add first version of command line tool for importing conditions from text files HPSJAVA-280 Added: java/trunk/conditions/src/main/java/org/hps/conditions/cli/ java/trunk/conditions/src/main/java/org/hps/conditions/cli/AbstractCommand.java (with props) java/trunk/conditions/src/main/java/org/hps/conditions/cli/CommandLineTool.java (with props) java/trunk/conditions/src/main/java/org/hps/conditions/cli/LoadCommand.java (with props) Modified: java/trunk/conditions/src/main/java/org/hps/conditions/QueryBuilder.java Modified: java/trunk/conditions/src/main/java/org/hps/conditions/QueryBuilder.java ============================================================================= --- java/trunk/conditions/src/main/java/org/hps/conditions/QueryBuilder.java (original) +++ java/trunk/conditions/src/main/java/org/hps/conditions/QueryBuilder.java Wed Oct 29 18:59:03 2014 @@ -1,16 +1,18 @@ package org.hps.conditions; + +import java.util.List; /** * This is a static utility class for building SQL queries for the conditions system. * * @author Jeremy McCormick <[log in to unmask]> */ -final class QueryBuilder { +public final class QueryBuilder { private QueryBuilder() { } - static String buildSelect(String tableName, int collectionId, String[] fields, String order) { + public static String buildSelect(String tableName, int collectionId, String[] fields, String order) { StringBuffer buff = new StringBuffer(); buff.append("SELECT "); if (fields == null) { @@ -32,7 +34,7 @@ return buff.toString(); } - static String buildUpdate(String tableName, int rowId, String[] fields, Object[] values) { + public static String buildUpdate(String tableName, int rowId, String[] fields, Object[] values) { if (fields.length != values.length) throw new IllegalArgumentException("The field and value arrays are different lengths."); StringBuffer buff = new StringBuffer(); @@ -45,7 +47,7 @@ return buff.toString(); } - static String buildInsert(String tableName, int collectionId, String[] fields, Object[] values) { + public static String buildInsert(String tableName, int collectionId, String[] fields, Object[] values) { if (fields.length != values.length) throw new IllegalArgumentException("The field and value arrays are different lengths."); StringBuffer buff = new StringBuffer(); @@ -60,12 +62,31 @@ buff.append(") "); return buff.toString(); } + + public static String buildInsert(String tableName, int collectionID, List<String> columnNames, List<List<String>> rows) { + StringBuffer buff = new StringBuffer(); + buff.append("INSERT INTO " + tableName + " ( collection_id"); + for (String column : columnNames) { + buff.append(", " + column); + } + buff.append(" ) VALUES "); + for (List<String> values : rows) { + buff.append("( "); + buff.append(collectionID); + for (String value : values) { + buff.append(", '" + value + "'"); + } + buff.append("), "); + } + buff.setLength(buff.length() - 2); + return buff.toString(); + } + - static String buildDelete(String tableName, int rowId) { + public static String buildDelete(String tableName, int rowId) { if (rowId <= 0) throw new IllegalArgumentException("Invalid row ID: " + rowId); String query = "DELETE FROM " + tableName + " WHERE id = " + rowId; return query; - } - + } } Added: java/trunk/conditions/src/main/java/org/hps/conditions/cli/AbstractCommand.java ============================================================================= --- java/trunk/conditions/src/main/java/org/hps/conditions/cli/AbstractCommand.java (added) +++ java/trunk/conditions/src/main/java/org/hps/conditions/cli/AbstractCommand.java Wed Oct 29 18:59:03 2014 @@ -0,0 +1,78 @@ +package org.hps.conditions.cli; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.Parser; +import org.apache.commons.cli.PosixParser; + +/** + * This is the API that sub-commands must implement in the conditions command line interface. + * + * @author Jeremy McCormick <[log in to unmask]> + */ +public abstract class AbstractCommand { + + String name; + String description; + Options options = new Options(); + Parser parser = new PosixParser(); + boolean verbose = false; + + /** + * Class constructor. + * @param name The string that invokes this command. + * @param description The description of this command. + */ + AbstractCommand(String name, String description) { + this.name = name; + this.description = description; + } + + /** + * Get the name of this command. + * @return A String of the name of this command. + */ + String getName() { + return this.name; + } + + /** + * Get the description of this command. + * @return A String of the description of this command. + */ + String getDescription() { + return this.description; + } + + /** + * Options for this command. + * @return Options object for this command. + */ + Options getOptions() { + return options; + } + + /** + * Print the usage of this sub-command. + * @param doExit Whether or not to exit after printing usage. + */ + void printUsage() { + HelpFormatter help = new HelpFormatter(); + help.printHelp(getName(), getOptions()); + } + + /** + * Set whether verbose output is enabled. + * @param verbose True to enable verbose output. + */ + void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + /** + * Execute the command with the arguments. + * This is the only method that sub-classes must implement. + * @param arguments The sub-command's arguments. + */ + abstract void execute(String[] arguments); +} Added: java/trunk/conditions/src/main/java/org/hps/conditions/cli/CommandLineTool.java ============================================================================= --- java/trunk/conditions/src/main/java/org/hps/conditions/cli/CommandLineTool.java (added) +++ java/trunk/conditions/src/main/java/org/hps/conditions/cli/CommandLineTool.java Wed Oct 29 18:59:03 2014 @@ -0,0 +1,147 @@ +package org.hps.conditions.cli; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.hps.conditions.DatabaseConditionsManager; + +/** + * <p> + * This class is a command-line tool for performing commands on the conditions database. + * It has sub-commands much like the cvs or svn clients. The only current implemented + * command is 'load' to import text files, but more will be added. + * <p> + * Command line options allow a custom connection properties file or XML configuration + * to be supplied by the user which will override the default. + * + * @author Jeremy McCormick <[log in to unmask]> + */ +public class CommandLineTool { + + Options options = new Options(); + Map<String, AbstractCommand> commands = new HashMap<String, AbstractCommand>(); + PosixParser parser = new PosixParser(); + static final String CONNECTION_PROPERTIES_RESOURCE = "/org/hps/conditions/config/conditions_dev.properties"; + static final String XML_CONFIG_RESOURCE = "/org/hps/conditions/config/conditions_dev.xml"; + DatabaseConditionsManager conditionsManager; + boolean verbose = false; + + public static void main(String[] arguments) { + CommandLineTool.create().run(arguments); + } + + void run(String[] arguments) { + try { + if (arguments.length == 0) { + printUsage(); + exit(0); + } + + CommandLine commandLine = null; + try { + commandLine = parser.parse(options, arguments, true); + } catch (ParseException e) { + e.printStackTrace(); + printUsage(); + exit(1); + } + + if (commandLine.hasOption("h") || commandLine.getArgs().length == 0) { + printUsage(); + exit(0); + } + + if (commandLine.hasOption("v")) { + verbose = true; + } + + setupConditionsManager(commandLine); + + String commandName = commandLine.getArgs()[0]; + + AbstractCommand command = commands.get(commandName); + if (command == null) { + throw new IllegalArgumentException("Unknown command " + commandName); + } + + String[] commandArguments = new String[commandLine.getArgs().length - 1]; + System.arraycopy(commandLine.getArgs(), 1, commandArguments, 0, commandArguments.length); + + command.setVerbose(verbose); + command.execute(commandArguments); + } catch (Exception e) { + e.printStackTrace(); + } finally { + conditionsManager.closeConnection(); + } + } + + private void setupConditionsManager(CommandLine commandLine) { + conditionsManager = new DatabaseConditionsManager(); + if (verbose) + conditionsManager.setLogLevel(Level.ALL); + else + conditionsManager.setLogLevel(Level.WARNING); + if (commandLine.hasOption("p")) { + File connectionPropertiesFile = new File(commandLine.getOptionValue("p")); + if (verbose) + System.out.println("using connection properties file " + connectionPropertiesFile.getPath()); + conditionsManager.setConnectionProperties(connectionPropertiesFile); + } else { + if (verbose) + System.out.println("using connection resource " + CONNECTION_PROPERTIES_RESOURCE); + conditionsManager.setConnectionResource(CONNECTION_PROPERTIES_RESOURCE); + } + if (commandLine.hasOption("x")) { + File xmlConfigFile = new File(commandLine.getOptionValue("x")); + conditionsManager.configure(xmlConfigFile); + if (verbose) + System.out.println("using XML config file " + xmlConfigFile.getPath()); + } else { + if (verbose) + System.out.println("using XML config resource " + XML_CONFIG_RESOURCE); + conditionsManager.configure(XML_CONFIG_RESOURCE); + } + conditionsManager.register(); + conditionsManager.openConnection(); + } + + void printUsage() { + HelpFormatter help = new HelpFormatter(); + StringBuffer s = new StringBuffer(); + for (String command : commands.keySet()) { + s.append(command + '\n'); + } + help.printHelp("CommandLineTool", "Commands:\n" + s.toString(), options, ""); + } + + void exit(int status) { + System.exit(status); + } + + void registerCommand(AbstractCommand command) { + if (commands.containsKey(command.getName())) { + throw new IllegalArgumentException("There is already a command called " + command.getName()); + } + commands.put(command.getName(), command); + } + + static CommandLineTool create() { + CommandLineTool cli = new CommandLineTool(); + cli.options.addOption(new Option("h", false, "Print help and exit")); + cli.options.addOption(new Option("v", false, "Enable verbose terminal output")); + cli.options.addOption(new Option("p", true, "Set the connection properties file")); + cli.options.addOption(new Option("x", true, "Set the conditions database XML configuration file")); + cli.registerCommand(new LoadCommand()); + return cli; + } + +} Added: java/trunk/conditions/src/main/java/org/hps/conditions/cli/LoadCommand.java ============================================================================= --- java/trunk/conditions/src/main/java/org/hps/conditions/cli/LoadCommand.java (added) +++ java/trunk/conditions/src/main/java/org/hps/conditions/cli/LoadCommand.java Wed Oct 29 18:59:03 2014 @@ -0,0 +1,143 @@ +package org.hps.conditions.cli; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; +import org.hps.conditions.DatabaseConditionsManager; +import org.hps.conditions.QueryBuilder; + +/** + * <p> + * This is a sub-command to add conditions data using an input text file. + * The file should be ASCII text that is tab or space delimited and includes headers with the names of + * the database columns. (These must match exactly!) The user must supply a table name as the target + * for the SQL insert. An optional collection ID can be supplied, which may not exist already in the table. + * Otherwise, the command will fail. By default, the next collection ID will be found by the conditions manager. + * <p> + * <pre> + * java -cp hps-distribution-bin.jar org.hps.conditions.cli.CommandLineTool -p conditions_dev_local.properties \ + * load -t scratch_svt_gains -f ./scratch_svt_gains.txt -c 1 + * </pre> + * + * @author Jeremy McCormick <[log in to unmask]> + */ +public class LoadCommand extends AbstractCommand { + + LoadCommand() { + super("load", "Load a set of conditions into the database from a text file"); + this.options.addOption(new Option("t", true, "Set the name of the target table in the database")); + this.options.addOption(new Option("c", true, "Set the collection ID of this conditions set")); + this.options.addOption(new Option("f", true, "Set the input data file")); + } + + @Override + public void execute(String[] arguments) { + CommandLine commandLine; + try { + commandLine = parser.parse(options, arguments); + } catch (ParseException e) { + throw new RuntimeException(e); + } + + String fileName = commandLine.getOptionValue("f"); + if (fileName == null) { + throw new IllegalArgumentException("Missing file argument."); + } + + String tableName = commandLine.getOptionValue("t"); + if (tableName == null) { + throw new IllegalArgumentException("Missing table name."); + } + + DatabaseConditionsManager conditionsManager = DatabaseConditionsManager.getInstance(); + if (conditionsManager == null) { + throw new RuntimeException("The DatabaseConditionsManager was not setup properly."); + } + + int collectionID; + if (commandLine.getOptionValue("c") != null) { + collectionID = Integer.parseInt(commandLine.getOptionValue("c")); + if (collectionExists(conditionsManager, tableName, collectionID)) { + throw new IllegalArgumentException("The user supplied collection ID " + collectionID + " already exists in this table."); + } + } else { + collectionID = conditionsManager.getNextCollectionID(tableName); + } + + List<String> columnNames = new ArrayList<String>(); + List<List<String>> rows = new ArrayList<List<String>>(); + parseFile(fileName, columnNames, rows); + + String insertSql = QueryBuilder.buildInsert(tableName, collectionID, columnNames, rows); + if (verbose) + System.out.println(insertSql); + List<Integer> IDs = conditionsManager.updateQuery(insertSql); + System.out.println("Inserted " + IDs.size() + " new rows into table " + tableName + " with collection_id " + collectionID); + } + + void parseFile(String fileName, List<String> columnNames, List<List<String>> rows) { + File inputFile = new File(fileName); + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(inputFile)); + String headerLine = reader.readLine(); + if (headerLine == null) { + throw new IllegalArgumentException("The file is empty."); + } + StringTokenizer tokenizer = new StringTokenizer(headerLine, " \t"); + while (tokenizer.hasMoreTokens()) { + columnNames.add(tokenizer.nextToken().trim()); + } + String line = null; + while ((line = reader.readLine()) != null) { + tokenizer = new StringTokenizer(line, " \t"); + List<String> row = new ArrayList<String>(); + while (tokenizer.hasMoreTokens()) { + row.add(tokenizer.nextToken().trim()); + } + rows.add(row); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + boolean collectionExists(DatabaseConditionsManager conditionsManager, String tableName, int collectionID) { + String sql = "SELECT * FROM " + tableName + " where collection_id = " + collectionID; + ResultSet resultSet = conditionsManager.selectQuery(sql); + try { + resultSet.last(); + } catch (SQLException e) { + e.printStackTrace(); + } + int rowCount = 0; + try { + rowCount = resultSet.getRow(); + } catch (SQLException e) { + e.printStackTrace(); + } + if (rowCount != 0) { + return true; + } else { + return false; + } + } +}