Java tutorial
package com.databasepreservation.cli; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import javax.naming.OperationNotSupportedException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.MissingOptionException; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionGroup; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.databasepreservation.model.exception.LicenseNotAcceptedException; import com.databasepreservation.model.modules.DatabaseExportModule; import com.databasepreservation.model.modules.DatabaseImportModule; import com.databasepreservation.model.modules.DatabaseModuleFactory; import com.databasepreservation.model.parameters.Parameter; import com.databasepreservation.model.parameters.ParameterGroup; import com.databasepreservation.model.parameters.Parameters; import com.databasepreservation.utils.MiscUtils; import net.xeoh.plugins.base.PluginManager; import net.xeoh.plugins.base.impl.PluginManagerFactory; /** * Handles command line interface. * * Uses lazy parsing of parameters. Which means that the parameters are parsed * implicitly when something is requested that required them to be processed * (example: get the specified import or export modules). * * @author Bruno Ferreira <bferreira@keep.pt> */ public class CLI { private static final Logger LOGGER = LoggerFactory.getLogger(CLI.class); private final ArrayList<DatabaseModuleFactory> factories; private final List<String> commandLineArguments; private DatabaseImportModule importModule; private String importModuleName; private String exportModuleName; private DatabaseExportModule exportModule; private boolean forceDisableEncryption = false; /** * Create a new CLI handler * * @param commandLineArguments * List of command line parameters as they are received by Main.main * @param databaseModuleFactories * List of available module factories */ public CLI(List<String> commandLineArguments, List<Class<? extends DatabaseModuleFactory>> databaseModuleFactories) { factories = new ArrayList<DatabaseModuleFactory>(); this.commandLineArguments = commandLineArguments; try { for (Class<? extends DatabaseModuleFactory> factoryClass : databaseModuleFactories) { factories.add(factoryClass.newInstance()); } } catch (InstantiationException e) { LOGGER.error("Error initializing CLI", e); } catch (IllegalAccessException e) { LOGGER.error("Error initializing CLI", e); } includePluginModules(); } /** * Create a new CLI handler * * @param commandLineArguments * List of command line parameters as they are received by Main.main * @param databaseModuleFactories * Array of available module factories */ public CLI(List<String> commandLineArguments, DatabaseModuleFactory... databaseModuleFactories) { factories = new ArrayList<DatabaseModuleFactory>(Arrays.asList(databaseModuleFactories)); this.commandLineArguments = commandLineArguments; includePluginModules(); } private void includePluginModules() { // find plugins in command line arguments String pluginString = null; Iterator<String> argsIterator = commandLineArguments.iterator(); while (argsIterator.hasNext()) { String arg = argsIterator.next(); if ("-p".equals(arg) || "--plugin".equals(arg)) { pluginString = argsIterator.next(); break; } else if (StringUtils.startsWith(arg, "--plugin=")) { // 9 is the size of the string "--plugin=" pluginString = arg.substring(9); break; } } if (pluginString != null) { for (String plugin : pluginString.split(";")) { PluginManager pm = PluginManagerFactory.createPluginManager(); try { URI pluginURI = new URI(plugin); if (pluginURI.getScheme() == null) { pluginURI = new URI("file://" + plugin); } pm.addPluginsFrom(pluginURI); } catch (URISyntaxException e) { LOGGER.warn("Plugin not found: " + plugin, e); } factories.add(pm.getPlugin(DatabaseModuleFactory.class)); } } } /** * Gets the database import module, obtained by parsing the parameters * * @return The database import module specified in the parameters * @throws ParseException * if there was an error parsing the command line parameters * @throws LicenseNotAcceptedException * if the license for using a module was not accepted */ public DatabaseImportModule getImportModule() throws ParseException, LicenseNotAcceptedException { if (importModule == null) { parse(commandLineArguments); } return importModule; } /** * Gets the database export module, obtained by parsing the parameters * * @return The database import module specified in the parameters * @throws ParseException * if there was an error parsing the command line parameters * @throws LicenseNotAcceptedException * if the license for using a module was not accepted */ public DatabaseExportModule getExportModule() throws ParseException, LicenseNotAcceptedException { if (exportModule == null) { parse(commandLineArguments); } return exportModule; } /** * Gets the name of the export module. Note that this method does not trigger * the lazy loading mechanism for parsing the parameters, so the value may be * null if no calls to getImportModule() or getExportModule() were made. * * @return The export module name. null if the command line parameters have * not been parsed yet */ public String getExportModuleName() { return exportModuleName; } /** * Gets the name of the import module. Note that this method does not trigger * the lazy loading mechanism for parsing the parameters, so the value may be * null if no calls to getImportModule() or getExportModule() were made. * * @return The import module name. null if the command line parameters have * not been parsed yet */ public String getImportModuleName() { return importModuleName; } /** * Outputs the help text to STDOUT */ public void printHelp() { printHelp(System.out); } /** * Discards import and export module instances and disables encryption. Next * time #parse is run, encryption will be disabled for modules that support * that option. */ public void disableEncryption() { forceDisableEncryption = true; importModule = null; exportModule = null; } /** * Parses the argument list and creates new import and export modules * * @param args * The command line arguments * @throws ParseException * If the arguments could not be parsed or are invalid */ private void parse(List<String> args) throws ParseException, LicenseNotAcceptedException { DatabaseModuleFactoriesPair databaseModuleFactoriesPair = getModuleFactories(args); try { importModuleName = databaseModuleFactoriesPair.getImportModuleFactory().getModuleName(); exportModuleName = databaseModuleFactoriesPair.getExportModuleFactory().getModuleName(); DatabaseModuleFactoriesArguments databaseModuleFactoriesArguments = getModuleArguments( databaseModuleFactoriesPair, args); if (forceDisableEncryption) { // inject disable encryption for import module for (Parameter parameter : databaseModuleFactoriesPair.getImportModuleFactory() .getImportModuleParameters().getParameters()) { if (parameter.longName().equalsIgnoreCase("disable-encryption")) { if (!databaseModuleFactoriesArguments.getImportModuleArguments().containsKey(parameter)) { databaseModuleFactoriesArguments.getImportModuleArguments().put(parameter, "true"); } break; } } // inject disable encryption for export module for (Parameter parameter : databaseModuleFactoriesPair.getExportModuleFactory() .getExportModuleParameters().getParameters()) { if (parameter.longName().equalsIgnoreCase("disable-encryption")) { if (!databaseModuleFactoriesArguments.getExportModuleArguments().containsKey(parameter)) { databaseModuleFactoriesArguments.getExportModuleArguments().put(parameter, "true"); } break; } } } // set import and export modules importModule = databaseModuleFactoriesPair.getImportModuleFactory() .buildImportModule(databaseModuleFactoriesArguments.getImportModuleArguments()); exportModule = databaseModuleFactoriesPair.getExportModuleFactory() .buildExportModule(databaseModuleFactoriesArguments.getExportModuleArguments()); } catch (OperationNotSupportedException e) { LOGGER.debug("OperationNotSupportedException", e); throw new ParseException("Module does not support the requested mode."); } } /** * Given the arguments, determines the DatabaseModuleFactory objects that * should be used to create the import and export modules * * @param args * The command line arguments * @return A pair of DatabaseModuleFactory objects containing the selected * import and export module factories * @throws ParseException * If the arguments could not be parsed or are invalid */ private DatabaseModuleFactoriesPair getModuleFactories(List<String> args) throws ParseException { // check if args contains exactly one import and one export module String importModuleName = null; String exportModuleName = null; int importModulesFound = 0; int exportModulesFound = 0; Iterator<String> argsIterator = args.iterator(); try { while (argsIterator.hasNext()) { String arg = argsIterator.next(); if ("-i".equals(arg) || "--import".equals(arg)) { importModuleName = argsIterator.next(); importModulesFound++; } else if ("-e".equals(arg) || "--export".equals(arg)) { exportModuleName = argsIterator.next(); exportModulesFound++; } else if (StringUtils.startsWith(arg, "--import=")) { // 9 is the size of the string "--import=" importModuleName = arg.substring(9); importModulesFound++; } else if (StringUtils.startsWith(arg, "--export=")) { // 9 is the size of the string "--export=" exportModuleName = arg.substring(9); exportModulesFound++; } } } catch (NoSuchElementException e) { LOGGER.debug("NoSuchElementException", e); throw new ParseException("Missing module name."); } if (importModulesFound != 1 || exportModulesFound != 1) { throw new ParseException("Exactly one import module and one export module must be specified."); } // check if both module names correspond to real module names DatabaseModuleFactory importModuleFactory = null; DatabaseModuleFactory exportModuleFactory = null; for (DatabaseModuleFactory factory : factories) { String moduleName = factory.getModuleName(); if (moduleName.equalsIgnoreCase(importModuleName) && factory.producesImportModules()) { importModuleFactory = factory; } if (moduleName.equalsIgnoreCase(exportModuleName) && factory.producesExportModules()) { exportModuleFactory = factory; } } if (importModuleFactory == null) { throw new ParseException("Invalid import module."); } else if (exportModuleFactory == null) { throw new ParseException("Invalid export module."); } return new DatabaseModuleFactoriesPair(importModuleFactory, exportModuleFactory); } /** * Obtains the arguments needed to create new import and export modules * * @param factoriesPair * A pair of DatabaseModuleFactory objects containing the selected * import and export module factories * @param args * The command line arguments * @return A DatabaseModuleFactoriesArguments containing the arguments to * create the import and export modules * @throws ParseException * If the arguments could not be parsed or are invalid */ private DatabaseModuleFactoriesArguments getModuleArguments(DatabaseModuleFactoriesPair factoriesPair, List<String> args) throws ParseException, OperationNotSupportedException { DatabaseModuleFactory importModuleFactory = factoriesPair.getImportModuleFactory(); DatabaseModuleFactory exportModuleFactory = factoriesPair.getExportModuleFactory(); // get appropriate command line options CommandLineParser commandLineParser = new DefaultParser(); CommandLine commandLine; Options options = new Options(); HashMap<String, Parameter> mapOptionToParameter = new HashMap<String, Parameter>(); for (Parameter parameter : importModuleFactory.getImportModuleParameters().getParameters()) { Option option = parameter.toOption("i", "import"); options.addOption(option); mapOptionToParameter.put(getUniqueOptionIdentifier(option), parameter); } for (ParameterGroup parameterGroup : importModuleFactory.getImportModuleParameters().getGroups()) { OptionGroup optionGroup = parameterGroup.toOptionGroup("i", "import"); options.addOptionGroup(optionGroup); for (Parameter parameter : parameterGroup.getParameters()) { mapOptionToParameter.put(getUniqueOptionIdentifier(parameter.toOption("i", "import")), parameter); } } for (Parameter parameter : exportModuleFactory.getExportModuleParameters().getParameters()) { Option option = parameter.toOption("e", "export"); options.addOption(option); mapOptionToParameter.put(getUniqueOptionIdentifier(option), parameter); } for (ParameterGroup parameterGroup : exportModuleFactory.getExportModuleParameters().getGroups()) { OptionGroup optionGroup = parameterGroup.toOptionGroup("e", "export"); options.addOptionGroup(optionGroup); for (Parameter parameter : parameterGroup.getParameters()) { mapOptionToParameter.put(getUniqueOptionIdentifier(parameter.toOption("e", "export")), parameter); } } Option importOption = Option.builder("i").longOpt("import").hasArg().optionalArg(false).build(); Option exportOption = Option.builder("e").longOpt("export").hasArg().optionalArg(false).build(); Option pluginOption = Option.builder("p").longOpt("plugin").hasArg().optionalArg(false).build(); options.addOption(importOption); options.addOption(exportOption); options.addOption(pluginOption); // new HelpFormatter().printHelp(80, "dbptk", "\nModule Options:", options, // null, true); // parse the command line arguments with those options try { commandLine = commandLineParser.parse(options, args.toArray(new String[] {}), false); if (!commandLine.getArgList().isEmpty()) { throw new ParseException("Unrecognized option: " + commandLine.getArgList().get(0)); } } catch (MissingOptionException e) { // use long names instead of short names in the error message List<String> missingShort = e.getMissingOptions(); List<String> missingLong = new ArrayList<String>(); for (String shortOption : missingShort) { missingLong.add(options.getOption(shortOption).getLongOpt()); } LOGGER.debug("MissingOptionException (the original, unmodified exception)", e); throw new MissingOptionException(missingLong); } // create arguments to pass to factory HashMap<Parameter, String> importModuleArguments = new HashMap<Parameter, String>(); HashMap<Parameter, String> exportModuleArguments = new HashMap<Parameter, String>(); for (Option option : commandLine.getOptions()) { Parameter p = mapOptionToParameter.get(getUniqueOptionIdentifier(option)); if (p != null) { if (isImportModuleOption(option)) { if (p.hasArgument()) { importModuleArguments.put(p, option.getValue(p.valueIfNotSet())); } else { importModuleArguments.put(p, p.valueIfSet()); } } else if (isExportModuleOption(option)) { if (p.hasArgument()) { exportModuleArguments.put(p, option.getValue(p.valueIfNotSet())); } else { exportModuleArguments.put(p, p.valueIfSet()); } } else { throw new ParseException("Unexpected parse exception occurred."); } } } return new DatabaseModuleFactoriesArguments(importModuleArguments, exportModuleArguments); } private void printHelp(PrintStream printStream) { StringBuilder out = new StringBuilder(); out.append("Database Preservation Toolkit").append(MiscUtils.APP_NAME_AND_VERSION) .append("\nMore info: http://www.database-preservation.com").append("\n") .append("Usage: dbptk [plugin] <importModule> [import module options] <exportModule> [export module options]\n\n"); ArrayList<DatabaseModuleFactory> modulesList = new ArrayList<DatabaseModuleFactory>(factories); Collections.sort(modulesList, new DatabaseModuleFactoryNameComparator()); out.append("## Plugin:\n"); out.append( " -p, --plugin=plugin.jar (optional) the file containing a plugin module. Several plugins can be specified, separated by a semi-colon (;)\n"); out.append("\n## Available import modules: -i <module>, --import=module\n"); for (DatabaseModuleFactory factory : modulesList) { if (factory.producesImportModules()) { try { out.append(printModuleHelp("Import module: " + factory.getModuleName(), "i", "import", factory.getImportModuleParameters())); } catch (OperationNotSupportedException e) { LOGGER.debug("This should not occur a this point", e); } } } out.append("\n## Available export modules: -e <module>, --export=module\n"); for (DatabaseModuleFactory factory : modulesList) { if (factory.producesExportModules()) { try { out.append(printModuleHelp("Export module: " + factory.getModuleName(), "e", "export", factory.getExportModuleParameters())); } catch (OperationNotSupportedException e) { LOGGER.debug("This should not occur a this point", e); } } } printStream.append(out).flush(); } private String printModuleHelp(String moduleDesignation, String shortParameterPrefix, String longParameterPrefix, Parameters moduleParameters) { StringBuilder out = new StringBuilder(); String space = " "; out.append("\n").append(moduleDesignation); for (Parameter parameter : moduleParameters.getParameters()) { out.append(printParameterHelp(space, shortParameterPrefix, longParameterPrefix, parameter)); } for (ParameterGroup parameterGroup : moduleParameters.getGroups()) { for (Parameter parameter : parameterGroup.getParameters()) { out.append(printParameterHelp(space, shortParameterPrefix, longParameterPrefix, parameter)); } } out.append("\n"); return out.toString(); } private String printParameterHelp(String space, String shortPrefix, String longPrefix, Parameter parameter) { StringBuilder out = new StringBuilder(); out.append("\n").append(space); if (StringUtils.isNotBlank(parameter.shortName())) { out.append("-").append(shortPrefix).append(parameter.shortName()).append(", "); } out.append("--").append(longPrefix).append("-").append(parameter.longName()); if (parameter.hasArgument()) { if (parameter.isOptionalArgument()) { out.append("["); } out.append("=value"); if (parameter.isOptionalArgument()) { out.append("]"); } } out.append(space); if (parameter.required()) { out.append("(required) "); } else { out.append("(optional) "); } out.append(parameter.description()); return out.toString(); } /** * Get operating system information. * * @return An order-preserving map which keys are a description (String) of * the information contained in the values (which are also of type * String). */ private HashMap<String, String> getOperatingSystemInfo() { LinkedHashMap<String, String> result = new LinkedHashMap<>(); result.put("Operating system", System.getProperty("os.name", "unknown")); result.put("Architecture", System.getProperty("os.arch", "unknown")); result.put("Version", System.getProperty("os.version", "unknown")); result.put("Java vendor", System.getProperty("java.vendor", "unknown")); result.put("Java version", System.getProperty("java.version", "unknown")); result.put("Java class version", System.getProperty("java.class.version", "unknown")); // Charset.defaultCharset() is bugged on java version 5 and fixed on java 6 result.put("Default Charset reported by java", Charset.defaultCharset().toString()); result.put("Default Charset used by StreamWriter", getDefaultCharSet()); result.put("file.encoding property", System.getProperty("file.encoding")); return result; } /** * Logs operating system information */ public void logOperatingSystemInfo() { for (Map.Entry<String, String> entry : getOperatingSystemInfo().entrySet()) { LOGGER.debug(entry.getKey() + ": " + entry.getValue()); } } public boolean shouldPrintHelp() { if (commandLineArguments.isEmpty()) { return true; } else if (commandLineArguments.size() == 1) { String arg = commandLineArguments.get(0); return "-h".equalsIgnoreCase(arg) || "--help".equalsIgnoreCase(arg); } else { return false; } } public boolean usingUTF8() { return Charset.defaultCharset().equals(Charset.forName("UTF-8")); } private static class DatabaseModuleFactoryNameComparator implements Comparator<DatabaseModuleFactory> { @Override public int compare(DatabaseModuleFactory o1, DatabaseModuleFactory o2) { return o1.getModuleName().compareTo(o2.getModuleName()); } } private static String getUniqueOptionIdentifier(Option option) { // some string that should never occur in option shortName nor longName final String delimiter = "\r\f\n"; return new StringBuilder().append(delimiter).append(option.getOpt()).append(delimiter) .append(option.getLongOpt()).append(delimiter).toString(); } private static boolean isImportModuleOption(Option option) { final String type = "i"; if (StringUtils.isNotBlank(option.getOpt())) { return option.getOpt().startsWith(type); } else if (StringUtils.isNotBlank(option.getLongOpt())) { return option.getLongOpt().startsWith(type); } return false; } private static boolean isExportModuleOption(Option option) { final String type = "e"; if (StringUtils.isNotBlank(option.getOpt())) { return option.getOpt().startsWith(type); } else if (StringUtils.isNotBlank(option.getLongOpt())) { return option.getLongOpt().startsWith(type); } return false; } private static String getDefaultCharSet() { OutputStreamWriter dummyWriter = new OutputStreamWriter(new ByteArrayOutputStream()); String encoding = dummyWriter.getEncoding(); return encoding; } /** * Pair containing the import and export module factories */ public class DatabaseModuleFactoriesPair { // left: import, right: export private final ImmutablePair<DatabaseModuleFactory, DatabaseModuleFactory> factories; /** * Create a new pair with an import module factory and an export module * factory * * @param importModuleFactory * the import module factory * @param exportModuleFactory * the export module factory */ public DatabaseModuleFactoriesPair(DatabaseModuleFactory importModuleFactory, DatabaseModuleFactory exportModuleFactory) { factories = new ImmutablePair<DatabaseModuleFactory, DatabaseModuleFactory>(importModuleFactory, exportModuleFactory); } /** * @return the import module */ public DatabaseModuleFactory getImportModuleFactory() { return factories.getLeft(); } /** * @return the import module */ public DatabaseModuleFactory getExportModuleFactory() { return factories.getRight(); } } /** * Pair containing the arguments to create the import and export modules */ public class DatabaseModuleFactoriesArguments { // left: import, right: export private final ImmutablePair<Map<Parameter, String>, Map<Parameter, String>> factories; /** * Create a new pair with the import module arguments and the export module * arguments * * @param importModuleArguments * import module arguments in the form Map<parameter, value parsed * from the command line> * @param exportModuleArguments * export module arguments in the form Map<parameter, value parsed * from the command line> */ public DatabaseModuleFactoriesArguments(Map<Parameter, String> importModuleArguments, Map<Parameter, String> exportModuleArguments) { factories = new ImmutablePair<Map<Parameter, String>, Map<Parameter, String>>(importModuleArguments, exportModuleArguments); } /** * @return import module arguments in the form Map<parameter, value parsed * from the command line> */ public Map<Parameter, String> getImportModuleArguments() { return factories.getLeft(); } /** * @return export module arguments in the form Map<parameter, value parsed * from the command line> */ public Map<Parameter, String> getExportModuleArguments() { return factories.getRight(); } } }