# HG changeset patch # User Omair Majid # Date 1359407017 18000 # Node ID 1936ad067842cf0eb3e86baca05fdef34924f530 # Parent 186115da601f88f72392cd24df2e97ffeb6b3398 Make it easier to install plugins Reviewed-by: jerboaa, neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005355.html PR 1254 diff -r 186115da601f -r 1936ad067842 common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfo.java --- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfo.java Wed Jan 16 18:58:59 2013 +0100 +++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfo.java Mon Jan 28 16:03:37 2013 -0500 @@ -50,6 +50,7 @@ public Options getOptions(); + /** Returns a list of jar that this command depends on */ public List getDependencyResourceNames(); } diff -r 186115da601f -r 1936ad067842 common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfoSource.java --- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfoSource.java Wed Jan 16 18:58:59 2013 +0100 +++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandInfoSource.java Mon Jan 28 16:03:37 2013 -0500 @@ -38,7 +38,6 @@ import java.util.Collection; - public interface CommandInfoSource { public CommandInfo getCommandInfo(String name) throws CommandInfoNotFoundException; diff -r 186115da601f -r 1936ad067842 common/core/src/main/java/com/redhat/thermostat/common/config/Configuration.java --- a/common/core/src/main/java/com/redhat/thermostat/common/config/Configuration.java Wed Jan 16 18:58:59 2013 +0100 +++ b/common/core/src/main/java/com/redhat/thermostat/common/config/Configuration.java Mon Jan 28 16:03:37 2013 -0500 @@ -68,6 +68,10 @@ return home + File.separator + THERMOSTAT_USER_DIR; } + public String getPluginRoot() throws InvalidConfigurationException { + return home + File.separator + "plugins"; + } + public File getBackendsBaseDirectory() throws InvalidConfigurationException { String loc = getThermostatHome() + File.separatorChar + "backends"; File file = new File(loc); diff -r 186115da601f -r 1936ad067842 distribution/pom.xml --- a/distribution/pom.xml Wed Jan 16 18:58:59 2013 +0100 +++ b/distribution/pom.xml Mon Jan 28 16:03:37 2013 -0500 @@ -196,6 +196,7 @@ + . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.util.List; + +import org.apache.commons.cli.Options; + +import com.redhat.thermostat.common.cli.CommandInfo; + +public class BasicCommandInfo implements CommandInfo { + + private final String name; + private final String description; + private final String usage; + private final Options options; + private final List resources; + + public BasicCommandInfo(String name, String description, String usage, Options options, List resources) { + this.name = name; + this.description = description; + this.usage = usage; + this.options = options; + this.resources = resources; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getUsage() { + return usage; + } + + @Override + public Options getOptions() { + return options; + } + + @Override + public List getDependencyResourceNames() { + return resources; + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfo.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,227 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.logging.Logger; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.common.utils.LoggingUtils; + +public class BuiltInCommandInfo implements CommandInfo { + + private static final Logger logger = LoggingUtils.getLogger(BuiltInCommandInfo.class); + private static final String PROPERTY_BUNDLES = "bundles"; + private static final String PROPERTY_DESC = "description"; + private static final String PROPERTY_USAGE = "usage"; + private static final String PROPERTY_OPTIONS = "options"; + + private static final String PROP_SHORTOPT = ".short"; + private static final String PROP_LONGOPT = ".long"; + private static final String PROP_OPTHASARG = ".hasarg"; + private static final String PROP_OPTREQUIRED = ".required"; + private static final String PROP_OPTDESC = ".description"; + + private String name, description, usage; + private Options options; + private List dependencies; + + BuiltInCommandInfo(String name, Properties properties, String libRoot) { + options = new Options(); + this.name = name; + for (Entry entry: properties.entrySet()) { + String key = (String) entry.getKey(); + if (key.equals(PROPERTY_BUNDLES)) { + learnDependencies((String) entry.getValue(), libRoot); + } else if (key.equals(PROPERTY_DESC)) { + description = properties.getProperty(key); + } else if (key.equals(PROPERTY_USAGE)) { + usage = properties.getProperty(key); + } else if (key.equals(PROPERTY_OPTIONS)) { + learnOptions((String) entry.getValue(), properties); + } + } + } + + private void learnDependencies(String bundlesValue, String libRoot) { + List resourceNames = Arrays.asList(bundlesValue.split(",")); + dependencies = new ArrayList<>(resourceNames.size()); + for (String value : resourceNames) { + String resource = value.trim(); + if (resource.length() == 0) { + continue; + } + File file = new File(libRoot, value.trim()); + String path = file.toURI().toString(); + if (!file.exists()) { + logger.severe("Bundle " + path + " required by " + getName() + + " command does not exist in the filesystem. This will cause" + + " osgi wiring issue when attempting to run this command."); + // Allow to proceed because this command may never be called. + } else { + dependencies.add(path); + } + } + } + + private void learnOptions(String optionsValue, Properties props) { + List optionNames = Arrays.asList(optionsValue.split(",")); + for (String optionString : optionNames) { + List optionsList = Arrays.asList(optionString.trim().split("\\|")); + if (optionsList.size() == 1) { + learnOption(optionsList.get(0).trim(), props); + } else { + learnOptionGroup(optionsList, props); + } + } + } + + private void learnOption(String name, Properties props) { + if (name.equals(CommonOptions.OPTIONS_COMMON_DB_OPTIONS)) { + addDbOptions(); + } else if (name.equals(CommonOptions.OPTIONS_COMMON_LOG_OPTION)) { + options.addOption(CommonOptions.getLogOption()); + } else { + Option option = optionFromProperties(name, props); + options.addOption(option); + } + } + + private void addDbOptions() { + for (Option opt: CommonOptions.getDbOptions()) { + options.addOption(opt); + } + } + + /* TODO currently this assumes that any set of mutually exclusive options will be + * required. Needs some sort of enhancement in properties file to allow them to + * be optional. For the time being this is good enough, since in practice all such + * sets *are* required. + */ + private void learnOptionGroup(List optionsList, Properties props) { + OptionGroup og = new OptionGroup(); + og.setRequired(true); + for (String optionName : optionsList) { + Option option = optionFromProperties(optionName.trim(), props); + og.addOption(option); + } + options.addOptionGroup(og); + } + + private Option optionFromProperties(String name, Properties props) { + String opt = null; + String longOpt = null; + boolean hasArg = false; + boolean required = false; + String description = null; + + String optKey = name + PROP_SHORTOPT; + String longKey = name + PROP_LONGOPT; + String argKey = name + PROP_OPTHASARG; + String requiredKey = name + PROP_OPTREQUIRED; + String descKey = name + PROP_OPTDESC; + + // required property of common options are allowed to be overridden by + // command.properties files + if (CommonOptions.ALL_COMMON_OPTIONS.contains(name) && options.hasOption(name)) { + if (props.containsKey(requiredKey)) { + Option optionToChange = options.getOption(name); + required = Boolean.parseBoolean((String) props.getProperty(requiredKey)); + optionToChange.setRequired(required); + return optionToChange; + } + } + + if (props.containsKey(optKey)) { + opt = (String) props.getProperty(optKey); + } + if (props.containsKey(longKey)) { + longOpt = (String) props.getProperty(longKey); + } + if (opt == null && longOpt == null) { + logger.severe("Neither short nor long version of option " + name + " was set. Check properties file."); + } + if (props.containsKey(argKey)) { + hasArg = Boolean.parseBoolean((String) props.getProperty(argKey)); + } else { + logger.warning("The 'hasarg' property for " + name + " was not set. Assuming FALSE"); + } + if (props.containsKey(requiredKey)) { + required = Boolean.parseBoolean((String) props.getProperty(requiredKey)); + } else { + logger.warning("The 'required' property for " + name + " was not set. Assuming FALSE"); + } + if (props.containsKey(descKey)) { + description = (String) props.getProperty(descKey); + } + + Option option = new Option(opt, longOpt, hasArg, description); + option.setArgName(name); + option.setRequired(required); + return option; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getUsage() { + return usage; + } + + public Options getOptions() { + return options; + } + + public List getDependencyResourceNames() { + return dependencies; + } +} + diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoSource.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoSource.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,111 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.io.File; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.common.cli.CommandInfoNotFoundException; +import com.redhat.thermostat.common.cli.CommandInfoSource; +import com.redhat.thermostat.common.utils.LoggingUtils; + +public class BuiltInCommandInfoSource implements CommandInfoSource { + + private static final Logger logger = LoggingUtils.getLogger(BuiltInCommandInfoSource.class); + private Map commands; + + BuiltInCommandInfoSource(String commandsDir, String libRoot) { + commands = new HashMap<>(); + final File dir = new File(commandsDir); + if (dir.isDirectory()) { + FilenameFilter filter = new FilenameFilter() { + + @Override + public boolean accept(File theDir, String filename) { + if (!theDir.equals(dir)) { + return false; + } + return filename.endsWith(".properties"); + } + + }; + File[] commandPropertyFiles = dir.listFiles(filter); + for (File file : commandPropertyFiles) { + Properties commandProps = new Properties(); + try { + commandProps.load(new FileReader(file)); + } catch (IOException ignore) { + // This means the command won't work, if it has dependencies it + // needs to load. Also, it will not appear in help listing. + logger.warning("Issue loading properties file: " + file.getPath()); + } + String commandName = deduceCommandName(file.getName()); + commands.put(commandName, new BuiltInCommandInfo(commandName, commandProps, libRoot)); + } + } else { + logger.warning("Command configuration directory not found or not a directory: " + dir.getPath()); + } + } + + private String deduceCommandName(String fileName) { + int dotIndex = fileName.lastIndexOf("."); + return fileName.substring(0, dotIndex); + } + + @Override + public CommandInfo getCommandInfo(String name) throws CommandInfoNotFoundException { + CommandInfo cmdInfo = commands.get(name); + if (cmdInfo == null) { + throw new CommandInfoNotFoundException(name); + } + return cmdInfo; + } + + @Override + public Collection getCommandInfos() { + return commands.values(); + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoImpl.java --- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoImpl.java Wed Jan 16 18:58:59 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,228 +0,0 @@ -/* - * Copyright 2012, 2013 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * . - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.launcher.internal; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.logging.Logger; - -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionGroup; -import org.apache.commons.cli.Options; - -import com.redhat.thermostat.common.cli.CommandInfo; -import com.redhat.thermostat.common.utils.LoggingUtils; - - -public class CommandInfoImpl implements CommandInfo { - - private static final Logger logger = LoggingUtils.getLogger(CommandInfoSourceImpl.class); - private static final String PROPERTY_BUNDLES = "bundles"; - private static final String PROPERTY_DESC = "description"; - private static final String PROPERTY_USAGE = "usage"; - private static final String PROPERTY_OPTIONS = "options"; - - private static final String PROP_SHORTOPT = ".short"; - private static final String PROP_LONGOPT = ".long"; - private static final String PROP_OPTHASARG = ".hasarg"; - private static final String PROP_OPTREQUIRED = ".required"; - private static final String PROP_OPTDESC = ".description"; - - private String name, description, usage; - private Options options; - private List dependencies; - - CommandInfoImpl(String name, Properties properties, String libRoot) { - options = new Options(); - this.name = name; - for (Entry entry: properties.entrySet()) { - String key = (String) entry.getKey(); - if (key.equals(PROPERTY_BUNDLES)) { - learnDependencies((String) entry.getValue(), libRoot); - } else if (key.equals(PROPERTY_DESC)) { - description = properties.getProperty(key); - } else if (key.equals(PROPERTY_USAGE)) { - usage = properties.getProperty(key); - } else if (key.equals(PROPERTY_OPTIONS)) { - learnOptions((String) entry.getValue(), properties); - } - } - } - - private void learnDependencies(String bundlesValue, String libRoot) { - List resourceNames = Arrays.asList(bundlesValue.split(",")); - dependencies = new ArrayList<>(resourceNames.size()); - for (String value : resourceNames) { - String resource = value.trim(); - if (resource.length() == 0) { - continue; - } - File file = new File(libRoot, value.trim()); - String path = file.toURI().toString(); - if (!file.exists()) { - logger.severe("Bundle " + path + " required by " + getName() + - " command does not exist in the filesystem. This will cause" + - " osgi wiring issue when attempting to run this command."); - // Allow to proceed because this command may never be called. - } else { - dependencies.add(path); - } - } - } - - private void learnOptions(String optionsValue, Properties props) { - List optionNames = Arrays.asList(optionsValue.split(",")); - for (String optionString : optionNames) { - List optionsList = Arrays.asList(optionString.trim().split("\\|")); - if (optionsList.size() == 1) { - learnOption(optionsList.get(0).trim(), props); - } else { - learnOptionGroup(optionsList, props); - } - } - } - - private void learnOption(String name, Properties props) { - if (name.equals(CommonOptions.OPTIONS_COMMON_DB_OPTIONS)) { - addDbOptions(); - } else if (name.equals(CommonOptions.OPTIONS_COMMON_LOG_OPTION)) { - options.addOption(CommonOptions.getLogOption()); - } else { - Option option = optionFromProperties(name, props); - options.addOption(option); - } - } - - private void addDbOptions() { - for (Option opt: CommonOptions.getDbOptions()) { - options.addOption(opt); - } - } - - /* TODO currently this assumes that any set of mutually exclusive options will be - * required. Needs some sort of enhancement in properties file to allow them to - * be optional. For the time being this is good enough, since in practice all such - * sets *are* required. - */ - private void learnOptionGroup(List optionsList, Properties props) { - OptionGroup og = new OptionGroup(); - og.setRequired(true); - for (String optionName : optionsList) { - Option option = optionFromProperties(optionName.trim(), props); - og.addOption(option); - } - options.addOptionGroup(og); - } - - private Option optionFromProperties(String name, Properties props) { - String opt = null; - String longOpt = null; - boolean hasArg = false; - boolean required = false; - String description = null; - - String optKey = name + PROP_SHORTOPT; - String longKey = name + PROP_LONGOPT; - String argKey = name + PROP_OPTHASARG; - String requiredKey = name + PROP_OPTREQUIRED; - String descKey = name + PROP_OPTDESC; - - // required property of common options are allowed to be overridden by - // command.properties files - if (CommonOptions.ALL_COMMON_OPTIONS.contains(name) && options.hasOption(name)) { - if (props.containsKey(requiredKey)) { - Option optionToChange = options.getOption(name); - required = Boolean.parseBoolean((String) props.getProperty(requiredKey)); - optionToChange.setRequired(required); - return optionToChange; - } - } - - if (props.containsKey(optKey)) { - opt = (String) props.getProperty(optKey); - } - if (props.containsKey(longKey)) { - longOpt = (String) props.getProperty(longKey); - } - if (opt == null && longOpt == null) { - logger.severe("Neither short nor long version of option " + name + " was set. Check properties file."); - } - if (props.containsKey(argKey)) { - hasArg = Boolean.parseBoolean((String) props.getProperty(argKey)); - } else { - logger.warning("The 'hasarg' property for " + name + " was not set. Assuming FALSE"); - } - if (props.containsKey(requiredKey)) { - required = Boolean.parseBoolean((String) props.getProperty(requiredKey)); - } else { - logger.warning("The 'required' property for " + name + " was not set. Assuming FALSE"); - } - if (props.containsKey(descKey)) { - description = (String) props.getProperty(descKey); - } - - Option option = new Option(opt, longOpt, hasArg, description); - option.setArgName(name); - option.setRequired(required); - return option; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public String getUsage() { - return usage; - } - - public Options getOptions() { - return options; - } - - public List getDependencyResourceNames() { - return dependencies; - } -} - diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceImpl.java --- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceImpl.java Wed Jan 16 18:58:59 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/* - * Copyright 2012, 2013 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * . - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.launcher.internal; - -import java.io.File; -import java.io.FileReader; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.logging.Logger; - -import com.redhat.thermostat.common.cli.CommandInfo; -import com.redhat.thermostat.common.cli.CommandInfoNotFoundException; -import com.redhat.thermostat.common.cli.CommandInfoSource; -import com.redhat.thermostat.common.utils.LoggingUtils; - -public class CommandInfoSourceImpl implements CommandInfoSource { - - private static final Logger logger = LoggingUtils.getLogger(CommandInfoSourceImpl.class); - private Map commands; - - CommandInfoSourceImpl(String commandsDir, String libRoot) { - commands = new HashMap<>(); - final File dir = new File(commandsDir); - if (dir.isDirectory()) { - FilenameFilter filter = new FilenameFilter() { - - @Override - public boolean accept(File theDir, String filename) { - if (!theDir.equals(dir)) { - return false; - } - return filename.endsWith(".properties"); - } - - }; - File[] commandPropertyFiles = dir.listFiles(filter); - for (File file : commandPropertyFiles) { - Properties commandProps = new Properties(); - try { - commandProps.load(new FileReader(file)); - } catch (IOException ignore) { - // This means the command won't work, if it has dependencies it - // needs to load. Also, it will not appear in help listing. - logger.warning("Issue loading properties file: " + file.getPath()); - } - String commandName = deduceCommandName(file.getName()); - commands.put(commandName, new CommandInfoImpl(commandName, commandProps, libRoot)); - } - } else { - logger.warning("Command configuration directory not found or not a directory: " + dir.getPath()); - } - } - - private String deduceCommandName(String fileName) { - int dotIndex = fileName.lastIndexOf("."); - return fileName.substring(0, dotIndex); - } - - @Override - public CommandInfo getCommandInfo(String name) throws CommandInfoNotFoundException { - CommandInfo cmdInfo = commands.get(name); - if (cmdInfo == null) { - throw new CommandInfoNotFoundException(name); - } - return cmdInfo; - } - - @Override - public Collection getCommandInfos() { - return commands.values(); - } - -} - diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/CompoundCommandInfoSource.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CompoundCommandInfoSource.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,133 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.cli.Options; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.common.cli.CommandInfoNotFoundException; +import com.redhat.thermostat.common.cli.CommandInfoSource; + +/** + * Presents multiple {@link CommandInfoSource}s as one + *

+ * Unfortunately, it can't just delegate requests; it has to merge them. + */ +public class CompoundCommandInfoSource implements CommandInfoSource { + + private final CommandInfoSource source1; + private final CommandInfoSource source2; + + public CompoundCommandInfoSource(CommandInfoSource source1, CommandInfoSource source2) { + this.source1 = Objects.requireNonNull(source1); + this.source2 = Objects.requireNonNull(source2); + } + + @Override + public CommandInfo getCommandInfo(String name) throws CommandInfoNotFoundException { + CommandInfo info1 = source1.getCommandInfo(name); + CommandInfo info2 = source2.getCommandInfo(name); + if (info1 == null) { + return info2; + } if (info2 == null) { + return info1; + } else { + return merge(info1, info2); + } + } + + @Override + public Collection getCommandInfos() { + return mergeAll(source1.getCommandInfos(), source2.getCommandInfos()); + } + + private Collection mergeAll(Collection commandInfos1, Collection commandInfos2) { + Map result = new HashMap<>(); + for (CommandInfo info : commandInfos1) { + result.put(info.getName(), info); + } + for (CommandInfo info : commandInfos2) { + String cmdName = info.getName(); + if (!result.containsKey(cmdName)) { + result.put(cmdName, info); + continue; + } + + result.put(cmdName, merge(result.get(cmdName), info)); + } + + return result.values(); + } + + private CommandInfo merge(CommandInfo info1, CommandInfo info2) { + if (!info1.getName().equals(info2.getName())) { + throw new IllegalArgumentException("command information have different names"); + } + String name = info1.getName(); + + String description = selectBest(info1.getDescription(), info2.getDescription()); + String usage = selectBest(info1.getUsage(), info2.getUsage()); + Options options = selectBest(info1.getOptions(), info2.getOptions()); + List resources = new ArrayList<>(); + resources.addAll(info1.getDependencyResourceNames()); + resources.addAll(info2.getDependencyResourceNames()); + + return new BasicCommandInfo(name, description, usage, options, resources); + } + + private T selectBest(T first, T second) { + T result; + if (Objects.equals(first, second)) { + result = first; + } else if (first == null) { + result = second; + } else if (second == null) { + result = first; + } else { + throw new IllegalArgumentException("two conflicting values!"); + } + return result; + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSource.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSource.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,132 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.common.cli.CommandInfoNotFoundException; +import com.redhat.thermostat.common.cli.CommandInfoSource; +import com.redhat.thermostat.common.utils.LoggingUtils; +import com.redhat.thermostat.launcher.internal.PluginConfiguration.CommandExtensions; + +public class PluginCommandInfoSource implements CommandInfoSource { + + private Logger logger = LoggingUtils.getLogger(PluginCommandInfoSource.class); + + private Map> allInfo = new HashMap<>(); + + public PluginCommandInfoSource(String internalJarRoot, String pluginRootDir) { + this(new File(internalJarRoot), new File(pluginRootDir), new PluginConfigurationParser()); + } + + public PluginCommandInfoSource(File internalJarRoot, File pluginRootDir, + PluginConfigurationParser parser) { + File[] pluginDirs = pluginRootDir.listFiles(); + if (pluginDirs == null) { + logger.log(Level.SEVERE, "plugin root dir " + pluginRootDir + " does not exist"); + return; + } + + for (File pluginDir : pluginDirs) { + try { + File configurationFile = new File(pluginDir, "plugin.conf"); + PluginConfiguration pluginConfig = parser.parse(configurationFile); + loadNewAndExtendedCommands(internalJarRoot, pluginDir, pluginConfig); + } catch (PluginConfigurationParseException | FileNotFoundException exception) { + logger.log(Level.WARNING, "unable to parse plugin configuration", exception); + } + } + } + + private void loadNewAndExtendedCommands(File coreJarRoot, File pluginDir, + PluginConfiguration pluginConfig) { + + List allExtensions = pluginConfig.getExtendedCommands(); + + for (CommandExtensions extension : allExtensions) { + String commandName = extension.getCommandName(); + List pluginBundles = extension.getAdditionalBundles(); + List dependencyBundles = extension.getDepenedencyBundles(); + logger.config("plugin at " + pluginDir + " contributes " + + pluginBundles.size() + " bundles to comamnd '" + commandName + "'"); + + List bundlePaths = allInfo.get(commandName); + if (bundlePaths == null) { + bundlePaths = new LinkedList<>(); + } + + for (String bundle : pluginBundles) { + bundlePaths.add(new File(pluginDir, bundle).toURI().toString()); + } + for (String bundle : dependencyBundles) { + bundlePaths.add(new File(coreJarRoot, bundle).toURI().toString()); + } + + allInfo.put(commandName, bundlePaths); + } + } + + @Override + public CommandInfo getCommandInfo(String name) throws CommandInfoNotFoundException { + List bundles = allInfo.get(name); + if (bundles == null) { + return null; + } + return new BasicCommandInfo(name, null, null, null, bundles); + } + + @Override + public Collection getCommandInfos() { + List result = new ArrayList<>(); + for (Entry> entry : allInfo.entrySet()) { + result.add(new BasicCommandInfo(entry.getKey(), null, null, null, entry.getValue())); + } + return result; + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfiguration.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfiguration.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,131 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.cli.Options; + +public class PluginConfiguration { + + private final List extensions; + private final List newCommands; + + public PluginConfiguration(List newCommands, List extensions) { + this.newCommands = newCommands; + this.extensions = extensions; + } + + public List getExtendedCommands() { + return extensions; + } + + public List getNewCommands() { + return newCommands; + } + + public static class CommandExtensions { + + private final String commandName; + private final List additionalResources; + private final List coreDeps; + + public CommandExtensions(String name, List additionalResources, List coreDeps) { + this.commandName = name; + this.additionalResources = additionalResources; + this.coreDeps = coreDeps; + } + + public String getCommandName() { + return commandName; + } + + public List getAdditionalBundles() { + return Collections.unmodifiableList(additionalResources); + } + + public List getDepenedencyBundles() { + return coreDeps; + } + } + + public static class NewCommand { + + private final String commandName; + private final String description; + private final String usage; + private final Options options; + private final List additionalResources; + private final List coreDeps; + + public NewCommand(String name, String usage, String description, + Options options, List additionalResources, List coreDeps) { + this.commandName = name; + this.description = description; + this.usage = usage; + this.options = options; + this.additionalResources = additionalResources; + this.coreDeps = coreDeps; + } + + public String getCommandName() { + return commandName; + } + + public String getDescription() { + return description; + } + + public String getUsage() { + return usage; + } + + public Options getOptions() { + return options; + } + + public List getAdditionalBundles() { + return Collections.unmodifiableList(additionalResources); + } + + public List getCoreDepenedencyBundles() { + return Collections.unmodifiableList(coreDeps); + } + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParseException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParseException.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +public class PluginConfigurationParseException extends RuntimeException { + + public PluginConfigurationParseException(String message) { + super(message); + } + + public PluginConfigurationParseException(String message, Throwable cause) { + super(message, cause); + } +} diff -r 186115da601f -r 1936ad067842 launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParser.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,194 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.cli.Options; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.redhat.thermostat.common.Pair; +import com.redhat.thermostat.launcher.internal.PluginConfiguration.CommandExtensions; +import com.redhat.thermostat.launcher.internal.PluginConfiguration.NewCommand; + +public class PluginConfigurationParser { + + // no state :) + + public PluginConfiguration parse(File configurationFile) throws FileNotFoundException { + return parse(new FileInputStream(configurationFile)); + } + + public PluginConfiguration parse(InputStream configurationStream) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document xmlDoc = builder.parse(configurationStream); + Node rootNode = xmlDoc.getFirstChild(); + if (rootNode == null) { + throw new PluginConfigurationParseException("no configuration found"); + } + return parseRootElement(rootNode); + } catch (ParserConfigurationException | SAXException | IOException exception) { + throw new PluginConfigurationParseException("failed to parse plugin configuration", exception); + } + } + + private PluginConfiguration parseRootElement(Node root) { + List newCommands = Collections.emptyList(); + List extensions = Collections.emptyList(); + + Pair, List> commands = new Pair<>(newCommands, extensions); + if (root.getNodeName().equals("plugin")) { + NodeList nodes = root.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeName().equals("commands")) { + commands = parseCommands(node); + } + } + } + + return new PluginConfiguration(commands.getFirst(), commands.getSecond()); + } + + private Pair, List> parseCommands(Node commandsNode) { + List newCommands = new ArrayList(); + List extendedCommands = new ArrayList(); + NodeList childNodes = commandsNode.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node.getNodeName().equals("new")) { + newCommands.add(parseNewCommand(node)); + } else if (node.getNodeName().equals("existing")) { + extendedCommands.add(parseAdditionsToExistingCommand(node)); + } + } + return new Pair<>(newCommands, extendedCommands); + } + + private CommandExtensions parseAdditionsToExistingCommand(Node commandNode) { + String name = null; + List bundles = new ArrayList<>(); + List dependencies = new ArrayList<>(); + + NodeList nodes = commandNode.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeName().equals("name")) { + name = node.getTextContent(); + } else if (node.getNodeName().equals("bundles")) { + String[] bundleNames = node.getTextContent().split(","); + for (String bundleName : bundleNames) { + if (bundleName.trim().length() == 0) { + continue; + } + bundles.add(bundleName.trim()); + } + } else if (node.getNodeName().equals("dependencies")) { + String[] dependencyNames = node.getTextContent().split(","); + for (String bundleName : dependencyNames) { + if (bundleName.trim().length() == 0) { + continue; + } + dependencies.add(bundleName); + } + } + } + return new CommandExtensions(name, bundles, dependencies); + } + + private NewCommand parseNewCommand(Node commandNode) { + String name = null; + String usage = null; + String description = null; + Options options = null; + List bundles = new ArrayList<>(); + List dependencies = new ArrayList<>(); + + NodeList nodes = commandNode.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeName().equals("name")) { + name = node.getTextContent(); + } else if (node.getNodeName().equals("usage")) { + usage = node.getTextContent(); + } else if (node.getNodeName().equals("description")) { + description = node.getTextContent(); + } else if (node.getNodeName().equals("arguments")) { + options = parseArguments(node); + } else if (node.getNodeName().equals("bundles")) { + String[] bundleNames = node.getTextContent().split(","); + for (String bundleName : bundleNames) { + if (bundleName.trim().length() == 0) { + continue; + } + bundles.add(bundleName); + } + } else if (node.getNodeName().equals("dependencies")) { + String[] dependencyNames = node.getTextContent().split(","); + for (String bundleName : dependencyNames) { + if (bundleName.trim().length() == 0) { + continue; + } + dependencies.add(bundleName); + } + } + } + return new NewCommand(name, usage, description, options, bundles, dependencies); + } + + private Options parseArguments(Node argumentsNode) { + // need to identify a way to express arguments + return null; + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java --- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java Wed Jan 16 18:58:59 2013 +0100 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -40,6 +40,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Matchers.isNull; @@ -56,6 +57,7 @@ import java.util.Hashtable; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -124,11 +126,24 @@ when(config.getThermostatHome()).thenReturn(""); when(registryService.getConfiguration()).thenReturn(config); - CommandInfoSourceImpl commands = mock(CommandInfoSourceImpl.class); - when(commands.getCommandInfos()).thenReturn(new ArrayList()); - whenNew(CommandInfoSourceImpl.class). + BuiltInCommandInfoSource source1 = mock(BuiltInCommandInfoSource.class); + when(source1.getCommandInfos()).thenReturn(new ArrayList()); + whenNew(BuiltInCommandInfoSource.class). withParameterTypes(String.class, String.class). - withArguments(isA(String.class), isA(String.class)).thenReturn(commands); + withArguments(isA(String.class), isA(String.class)).thenReturn(source1); + + PluginCommandInfoSource source2 = mock(PluginCommandInfoSource.class); + when(source2.getCommandInfos()).thenReturn(new ArrayList()); + whenNew(PluginCommandInfoSource.class) + .withParameterTypes(String.class, String.class) + .withArguments(anyString(), anyString()) + .thenReturn(source2); + + CompoundCommandInfoSource commands = mock(CompoundCommandInfoSource.class); + whenNew(CompoundCommandInfoSource.class) + .withParameterTypes(CommandInfoSource.class, CommandInfoSource.class) + .withArguments(source1, source2) + .thenReturn(commands); tracker = mock(MultipleServiceTracker.class); whenNew(MultipleServiceTracker.class). @@ -180,13 +195,13 @@ ServiceReference ref = context.getServiceReference(Keyring.class); customizer.addingService(ref); - assertTrue(context.isServiceRegistered(CommandInfoSource.class.getName(), mock(CommandInfoSourceImpl.class).getClass())); + assertTrue(context.isServiceRegistered(CommandInfoSource.class.getName(), mock(CompoundCommandInfoSource.class).getClass())); assertTrue(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class)); assertTrue(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class)); customizer.removedService(null, null); - assertFalse(context.isServiceRegistered(CommandInfoSource.class.getName(), CommandInfoSourceImpl.class)); + assertFalse(context.isServiceRegistered(CommandInfoSource.class.getName(), CompoundCommandInfoSource.class)); assertFalse(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class)); assertFalse(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class)); } diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/BasicCommandInfoTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BasicCommandInfoTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,66 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.cli.Options; +import org.junit.Test; + +public class BasicCommandInfoTest { + + @Test + public void testBasics() { + final String NAME = "name"; + final String DESCRIPTION = "description"; + final String USAGE = "usage"; + final Options OPTIONS = new Options(); + final List RESOURCES = Collections.emptyList(); + + BasicCommandInfo info = new BasicCommandInfo(NAME, DESCRIPTION, USAGE, OPTIONS, RESOURCES); + + assertEquals(NAME, info.getName()); + assertEquals(DESCRIPTION, info.getDescription()); + assertEquals(USAGE, info.getUsage()); + assertEquals(OPTIONS, info.getOptions()); + assertEquals(RESOURCES, info.getDependencyResourceNames()); + + } +} diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoSourceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoSourceTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,119 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.launcher.internal.BuiltInCommandInfoSource; + +public class BuiltInCommandInfoSourceTest { + + private Path tempThermostatHome; + + private File tempLibs; + private File tempEtc; + private File tempCommands; + private File tempPropsFile; + + @Before + public void setUp() throws IOException { + + tempThermostatHome = Files.createTempDirectory("test"); + tempThermostatHome.toFile().deleteOnExit(); + System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString()); + + tempLibs = new File(tempThermostatHome.toFile(), "libs"); + + tempEtc = new File(tempThermostatHome.toFile(), "etc"); + tempEtc.mkdirs(); + tempEtc.deleteOnExit(); + + tempCommands = new File(tempEtc, "commands"); + tempCommands.mkdirs(); + tempCommands.deleteOnExit(); + + Properties props = new Properties(); // Don't need to put anything in here. + writeProperties(props); + } + + private void writeProperties(Properties props) { + tempPropsFile = new File(tempCommands, "foo.properties"); + try { + props.store(new FileOutputStream(tempPropsFile), "Nothing here matters. It's a comment."); + } catch (IOException e) { + // The test setup is broken; the test hasn't started yet. + throw new RuntimeException("Exception was thrown while setting up for test.", e); + } + tempPropsFile.deleteOnExit(); + } + + @Test + public void testGetCommandInfo() { + BuiltInCommandInfoSource bundles = + new BuiltInCommandInfoSource(tempCommands.toString(), tempLibs.toString()); + CommandInfo info = bundles.getCommandInfo("foo"); + assertNotNull(info); + assertEquals("foo", info.getName()); + } + + @Test + public void testGetCommandInfos() { + BuiltInCommandInfoSource bundles = + new BuiltInCommandInfoSource(tempCommands.toString(), tempLibs.toString()); + Collection infos = bundles.getCommandInfos(); + assertNotNull(infos); + assertEquals(1, infos.size()); + CommandInfo info = infos.iterator().next(); + assertNotNull(info); + assertEquals("foo", info.getName()); + } + +} + diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BuiltInCommandInfoTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,266 @@ +/* + * Copyright 2012, 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.common.locale.Translate; + +public class BuiltInCommandInfoTest { + + private Path tempThermostatHome, someJarName1, someJarName2, missingJarName; + private File tempLibs; + + @Before + public void setUp() throws IOException { + tempThermostatHome = Files.createTempDirectory("test"); + tempThermostatHome.toFile().deleteOnExit(); + System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString()); + + tempLibs = new File(tempThermostatHome.toFile(), "libs"); + tempLibs.mkdirs(); + tempLibs.deleteOnExit(); + + File someJar1 = new File(tempLibs, "thermostat-osgi-fluff1.jar"); + someJar1.createNewFile(); + someJar1.deleteOnExit(); + someJarName1 = someJar1.toPath(); + + File someJar2 = new File(tempLibs, "thermostat-osgi-fluff2.jar"); + someJar2.createNewFile(); + someJar2.deleteOnExit(); + someJarName2 = someJar2.toPath(); + + File missingJar = new File(tempLibs, "thisjar_noexist.jar"); + missingJarName = missingJar.toPath(); + } + + private String resolvedJar(Path jar) { + return "file:" + jar.toString(); + } + + @Test + public void verifyGetName() { + Properties props = new Properties(); + String name = "name"; + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, ""); + + String commandName = info.getName(); + assertEquals(name, commandName); + } + + @Test + public void verifySingleResource() { + Properties props = new Properties(); + props.setProperty("bundles", someJarName1.getFileName().toString()); + String name = "name"; + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + List resources = info.getDependencyResourceNames(); + assertEquals(1, resources.size()); + assertTrue(resources.contains(resolvedJar(someJarName1))); + } + + @Test + public void verifyMultipleResources() { + Properties props = new Properties(); + props.setProperty("bundles", someJarName1.getFileName() + "," + someJarName2.getFileName()); + String name = "name"; + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + List resources = info.getDependencyResourceNames(); + assertEquals(2, resources.size()); + assertTrue(resources.contains(resolvedJar(someJarName1))); + assertTrue(resources.contains(resolvedJar(someJarName2))); + } + + @Test + public void verifyMissingResource() { + Properties props = new Properties(); + props.setProperty("bundles", missingJarName.getFileName().toString()); + String name = "name"; + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + List resources = info.getDependencyResourceNames(); + assertEquals(0, resources.size()); + assertFalse(resources.contains(resolvedJar(missingJarName))); + } + + @Test + public void verifyGetDescription() { + Properties props = new Properties(); + String name = "name"; + String desc = "desc"; + props.put("description", desc); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + String commandDesc = info.getDescription(); + assertEquals(desc, commandDesc); + } + + @Test + public void verifyGetUsage() { + Properties props = new Properties(); + String name = "name"; + String usage = "some sort of usage message"; + props.put("usage", usage); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + String commandUsage = info.getUsage(); + assertEquals(usage, commandUsage); + } + + @Test + public void verifyGetOptions() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "foo, bar"); + props.put("foo.short", "f"); + props.put("foo.long", "foo"); + props.put("foo.hasarg", "true"); + props.put("foo.required", "TRUE"); + props.put("foo.description", "the foo option"); + props.put("bar.short", "b"); + props.put("bar.long", "bar"); + props.put("bar.hasarg", "FALSE"); + props.put("bar.required", "this will evaluate as false"); + props.put("bar.description", "the bar option"); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + Options options = info.getOptions(); + Option foo = options.getOption("foo"); + assertEquals("foo", foo.getArgName()); + assertEquals("f", foo.getOpt()); + assertEquals("foo", foo.getLongOpt()); + assertTrue(foo.hasArg()); + assertTrue(foo.isRequired()); + assertEquals("the foo option", foo.getDescription()); + Option bar = options.getOption("bar"); + assertEquals("bar", bar.getArgName()); + assertEquals("b", bar.getOpt()); + assertEquals("bar", bar.getLongOpt()); + assertFalse(bar.hasArg()); + assertFalse(bar.isRequired()); + assertEquals("the bar option", bar.getDescription()); + } + + @Test + public void canAddCommonDBOptions() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_DB_OPTIONS"); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + Options options = info.getOptions(); + assertTrue(options.hasOption(CommonOptions.DB_URL_ARG)); + assertTrue(options.hasOption(CommonOptions.USERNAME_ARG)); + assertTrue(options.hasOption(CommonOptions.PASSWORD_ARG)); + assertFalse(options.getOption(CommonOptions.DB_URL_ARG).isRequired()); + Option dbUrlOption = options.getOption(CommonOptions.DB_URL_ARG); + Translate t = LocaleResources.createLocalizer(); + assertEquals(t.localize(LocaleResources.OPTION_DB_URL_DESC), dbUrlOption.getDescription()); + assertEquals("d", dbUrlOption.getOpt()); + assertEquals("dbUrl", dbUrlOption.getLongOpt()); + } + + @Test + public void requiredCommandPropertyOverridesCommonDbOptions() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_DB_OPTIONS, dbUrl"); + props.put("dbUrl.long", "ignored"); + props.put("dbUrl.required", "true"); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + Options options = info.getOptions(); + assertTrue(options.hasOption(CommonOptions.DB_URL_ARG)); + Option dbUrlOption = options.getOption(CommonOptions.DB_URL_ARG); + assertTrue(dbUrlOption.isRequired()); + assertEquals("dbUrl", dbUrlOption.getLongOpt()); + } + + @Test + public void canAddLogOption() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "AUTO_LOG_OPTION"); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + Options options = info.getOptions(); + assertTrue(options.hasOption(CommonOptions.LOG_LEVEL_ARG)); + assertFalse(options.getOption(CommonOptions.LOG_LEVEL_ARG).isRequired()); + } + + @Test + public void verifyOptionGroup() { + Properties props = new Properties(); + String name = "name"; + props.put("options", "foo|bar"); + props.put("foo.short", "f"); + props.put("bar.short", "b"); + BuiltInCommandInfo info = new BuiltInCommandInfo(name, props, tempLibs.toString()); + + Options options = info.getOptions(); + Option foo = options.getOption("f"); + assertNotNull(foo); + OptionGroup og = options.getOptionGroup(foo); + assertNotNull(og); + Option bar = options.getOption("b"); + @SuppressWarnings("rawtypes") + Collection members = og.getOptions(); + assertTrue(members.contains(foo)); + assertTrue(members.contains(bar)); + } +} + diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoImplTest.java --- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoImplTest.java Wed Jan 16 18:58:59 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,266 +0,0 @@ -/* - * Copyright 2012, 2013 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * . - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.launcher.internal; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Properties; - -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionGroup; -import org.apache.commons.cli.Options; -import org.junit.Before; -import org.junit.Test; - -import com.redhat.thermostat.common.locale.Translate; - -public class CommandInfoImplTest { - - private Path tempThermostatHome, someJarName1, someJarName2, missingJarName; - private File tempLibs; - - @Before - public void setUp() throws IOException { - tempThermostatHome = Files.createTempDirectory("test"); - tempThermostatHome.toFile().deleteOnExit(); - System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString()); - - tempLibs = new File(tempThermostatHome.toFile(), "libs"); - tempLibs.mkdirs(); - tempLibs.deleteOnExit(); - - File someJar1 = new File(tempLibs, "thermostat-osgi-fluff1.jar"); - someJar1.createNewFile(); - someJar1.deleteOnExit(); - someJarName1 = someJar1.toPath(); - - File someJar2 = new File(tempLibs, "thermostat-osgi-fluff2.jar"); - someJar2.createNewFile(); - someJar2.deleteOnExit(); - someJarName2 = someJar2.toPath(); - - File missingJar = new File(tempLibs, "thisjar_noexist.jar"); - missingJarName = missingJar.toPath(); - } - - private String resolvedJar(Path jar) { - return "file:" + jar.toString(); - } - - @Test - public void verifyGetName() { - Properties props = new Properties(); - String name = "name"; - CommandInfoImpl info = new CommandInfoImpl(name, props, ""); - - String commandName = info.getName(); - assertEquals(name, commandName); - } - - @Test - public void verifySingleResource() { - Properties props = new Properties(); - props.setProperty("bundles", someJarName1.getFileName().toString()); - String name = "name"; - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - List resources = info.getDependencyResourceNames(); - assertEquals(1, resources.size()); - assertTrue(resources.contains(resolvedJar(someJarName1))); - } - - @Test - public void verifyMultipleResources() { - Properties props = new Properties(); - props.setProperty("bundles", someJarName1.getFileName() + "," + someJarName2.getFileName()); - String name = "name"; - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - List resources = info.getDependencyResourceNames(); - assertEquals(2, resources.size()); - assertTrue(resources.contains(resolvedJar(someJarName1))); - assertTrue(resources.contains(resolvedJar(someJarName2))); - } - - @Test - public void verifyMissingResource() { - Properties props = new Properties(); - props.setProperty("bundles", missingJarName.getFileName().toString()); - String name = "name"; - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - List resources = info.getDependencyResourceNames(); - assertEquals(0, resources.size()); - assertFalse(resources.contains(resolvedJar(missingJarName))); - } - - @Test - public void verifyGetDescription() { - Properties props = new Properties(); - String name = "name"; - String desc = "desc"; - props.put("description", desc); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - String commandDesc = info.getDescription(); - assertEquals(desc, commandDesc); - } - - @Test - public void verifyGetUsage() { - Properties props = new Properties(); - String name = "name"; - String usage = "some sort of usage message"; - props.put("usage", usage); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - String commandUsage = info.getUsage(); - assertEquals(usage, commandUsage); - } - - @Test - public void verifyGetOptions() { - Properties props = new Properties(); - String name = "name"; - props.put("options", "foo, bar"); - props.put("foo.short", "f"); - props.put("foo.long", "foo"); - props.put("foo.hasarg", "true"); - props.put("foo.required", "TRUE"); - props.put("foo.description", "the foo option"); - props.put("bar.short", "b"); - props.put("bar.long", "bar"); - props.put("bar.hasarg", "FALSE"); - props.put("bar.required", "this will evaluate as false"); - props.put("bar.description", "the bar option"); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - Options options = info.getOptions(); - Option foo = options.getOption("foo"); - assertEquals("foo", foo.getArgName()); - assertEquals("f", foo.getOpt()); - assertEquals("foo", foo.getLongOpt()); - assertTrue(foo.hasArg()); - assertTrue(foo.isRequired()); - assertEquals("the foo option", foo.getDescription()); - Option bar = options.getOption("bar"); - assertEquals("bar", bar.getArgName()); - assertEquals("b", bar.getOpt()); - assertEquals("bar", bar.getLongOpt()); - assertFalse(bar.hasArg()); - assertFalse(bar.isRequired()); - assertEquals("the bar option", bar.getDescription()); - } - - @Test - public void canAddCommonDBOptions() { - Properties props = new Properties(); - String name = "name"; - props.put("options", "AUTO_DB_OPTIONS"); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - Options options = info.getOptions(); - assertTrue(options.hasOption(CommonOptions.DB_URL_ARG)); - assertTrue(options.hasOption(CommonOptions.USERNAME_ARG)); - assertTrue(options.hasOption(CommonOptions.PASSWORD_ARG)); - assertFalse(options.getOption(CommonOptions.DB_URL_ARG).isRequired()); - Option dbUrlOption = options.getOption(CommonOptions.DB_URL_ARG); - Translate t = LocaleResources.createLocalizer(); - assertEquals(t.localize(LocaleResources.OPTION_DB_URL_DESC), dbUrlOption.getDescription()); - assertEquals("d", dbUrlOption.getOpt()); - assertEquals("dbUrl", dbUrlOption.getLongOpt()); - } - - @Test - public void requiredCommandPropertyOverridesCommonDbOptions() { - Properties props = new Properties(); - String name = "name"; - props.put("options", "AUTO_DB_OPTIONS, dbUrl"); - props.put("dbUrl.long", "ignored"); - props.put("dbUrl.required", "true"); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - Options options = info.getOptions(); - assertTrue(options.hasOption(CommonOptions.DB_URL_ARG)); - Option dbUrlOption = options.getOption(CommonOptions.DB_URL_ARG); - assertTrue(dbUrlOption.isRequired()); - assertEquals("dbUrl", dbUrlOption.getLongOpt()); - } - - @Test - public void canAddLogOption() { - Properties props = new Properties(); - String name = "name"; - props.put("options", "AUTO_LOG_OPTION"); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - Options options = info.getOptions(); - assertTrue(options.hasOption(CommonOptions.LOG_LEVEL_ARG)); - assertFalse(options.getOption(CommonOptions.LOG_LEVEL_ARG).isRequired()); - } - - @Test - public void verifyOptionGroup() { - Properties props = new Properties(); - String name = "name"; - props.put("options", "foo|bar"); - props.put("foo.short", "f"); - props.put("bar.short", "b"); - CommandInfoImpl info = new CommandInfoImpl(name, props, tempLibs.toString()); - - Options options = info.getOptions(); - Option foo = options.getOption("f"); - assertNotNull(foo); - OptionGroup og = options.getOptionGroup(foo); - assertNotNull(og); - Option bar = options.getOption("b"); - @SuppressWarnings("rawtypes") - Collection members = og.getOptions(); - assertTrue(members.contains(foo)); - assertTrue(members.contains(bar)); - } -} - diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceTest.java --- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceTest.java Wed Jan 16 18:58:59 2013 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/* - * Copyright 2012, 2013 Red Hat, Inc. - * - * This file is part of Thermostat. - * - * Thermostat is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published - * by the Free Software Foundation; either version 2, or (at your - * option) any later version. - * - * Thermostat 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Thermostat; see the file COPYING. If not see - * . - * - * Linking this code with other modules is making a combined work - * based on this code. Thus, the terms and conditions of the GNU - * General Public License cover the whole combination. - * - * As a special exception, the copyright holders of this code give - * you permission to link this code with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also - * meet, for each linked independent module, the terms and conditions - * of the license of that module. An independent module is a module - * which is not derived from or based on this code. If you modify - * this code, you may extend this exception to your version of the - * library, but you are not obligated to do so. If you do not wish - * to do so, delete this exception statement from your version. - */ - -package com.redhat.thermostat.launcher.internal; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Properties; - -import org.junit.Before; -import org.junit.Test; - -import com.redhat.thermostat.common.cli.CommandInfo; -import com.redhat.thermostat.launcher.internal.CommandInfoSourceImpl; - -public class CommandInfoSourceTest { - - private Path tempThermostatHome; - - private File tempLibs; - private File tempEtc; - private File tempCommands; - private File tempPropsFile; - - @Before - public void setUp() throws IOException { - - tempThermostatHome = Files.createTempDirectory("test"); - tempThermostatHome.toFile().deleteOnExit(); - System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString()); - - tempLibs = new File(tempThermostatHome.toFile(), "libs"); - - tempEtc = new File(tempThermostatHome.toFile(), "etc"); - tempEtc.mkdirs(); - tempEtc.deleteOnExit(); - - tempCommands = new File(tempEtc, "commands"); - tempCommands.mkdirs(); - tempCommands.deleteOnExit(); - - Properties props = new Properties(); // Don't need to put anything in here. - writeProperties(props); - } - - private void writeProperties(Properties props) { - tempPropsFile = new File(tempCommands, "foo.properties"); - try { - props.store(new FileOutputStream(tempPropsFile), "Nothing here matters. It's a comment."); - } catch (IOException e) { - // The test setup is broken; the test hasn't started yet. - throw new RuntimeException("Exception was thrown while setting up for test.", e); - } - tempPropsFile.deleteOnExit(); - } - - @Test - public void testGetCommandInfo() { - CommandInfoSourceImpl bundles = - new CommandInfoSourceImpl(tempCommands.toString(), tempLibs.toString()); - CommandInfo info = bundles.getCommandInfo("foo"); - assertNotNull(info); - assertEquals("foo", info.getName()); - } - - @Test - public void testGetCommandInfos() { - CommandInfoSourceImpl bundles = - new CommandInfoSourceImpl(tempCommands.toString(), tempLibs.toString()); - Collection infos = bundles.getCommandInfos(); - assertNotNull(infos); - assertEquals(1, infos.size()); - CommandInfo info = infos.iterator().next(); - assertNotNull(info); - assertEquals("foo", info.getName()); - } - -} - diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSourceTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginCommandInfoSourceTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,133 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.redhat.thermostat.common.cli.CommandInfo; +import com.redhat.thermostat.launcher.internal.PluginConfiguration.CommandExtensions; + +public class PluginCommandInfoSourceTest { + + // name paths so anything tying to use them/create them will blow up + private static final String PLUGIN_ROOT = "/fake/${PLUGIN_ROOT}"; + private static final String JAR_ROOT = "/fake/${JAR_ROOT}"; + private File jarRootDir; + private File pluginRootDir; + private PluginConfigurationParser parser; + private PluginConfiguration parserResult; + + @Before + public void setUp() throws FileNotFoundException { + parser = mock(PluginConfigurationParser.class); + parserResult = mock(PluginConfiguration.class); + when(parser.parse(isA(File.class))).thenReturn(parserResult); + pluginRootDir = mock(File.class); + jarRootDir = new File(JAR_ROOT); + } + + @Test + public void verifyParserIsInvokedOnAllConfigurationFiles() throws FileNotFoundException { + File[] pluginDirs = new File[] { + new File(PLUGIN_ROOT, "plugin1"), + new File(PLUGIN_ROOT, "plugin2"), + }; + + when(pluginRootDir.listFiles()).thenReturn(pluginDirs); + + PluginCommandInfoSource source = new PluginCommandInfoSource(jarRootDir, pluginRootDir, parser); + + ArgumentCaptor configFilesCaptor = ArgumentCaptor.forClass(File.class); + verify(parser, times(pluginDirs.length)).parse(configFilesCaptor.capture()); + + List configurationFiles = configFilesCaptor.getAllValues(); + assertEquals(pluginDirs.length, configurationFiles.size()); + for (int i = 0; i < pluginDirs.length; i++) { + assertEquals(new File(pluginDirs[i], "plugin.conf"), configurationFiles.get(i)); + } + } + + @Test + public void verifyMissingConfigurationFileIsHandledCorrectly() throws FileNotFoundException { + File[] pluginDirs = new File[] { new File(PLUGIN_ROOT, "plugin1") }; + + when(pluginRootDir.listFiles()).thenReturn(pluginDirs); + when(parser.parse(isA(File.class))).thenThrow(new FileNotFoundException("test")); + + PluginCommandInfoSource source = new PluginCommandInfoSource(jarRootDir, pluginRootDir, parser); + } + + @Test + public void verifyCommandInfoObjectsToExtendExistingCommandsAreCreated() { + CommandExtensions extensions = mock(CommandExtensions.class); + when(extensions.getCommandName()).thenReturn("command-name"); + when(extensions.getAdditionalBundles()).thenReturn(Arrays.asList("additional-bundle")); + when(extensions.getDepenedencyBundles()).thenReturn(Arrays.asList("dependency-bundle")); + + when(parserResult.getExtendedCommands()).thenReturn(Arrays.asList(extensions)); + + File[] pluginDirs = new File[] { new File(PLUGIN_ROOT, "plugin1") }; + when(pluginRootDir.listFiles()).thenReturn(pluginDirs); + + PluginCommandInfoSource source = new PluginCommandInfoSource(jarRootDir, pluginRootDir, parser); + + CommandInfo info = source.getCommandInfo("command-name"); + assertEquals("command-name", info.getName()); + + String expectedDep1Name = new File(PLUGIN_ROOT + "/plugin1/additional-bundle").toURI().toString(); + String expectedDep2Name = new File(JAR_ROOT + "/dependency-bundle").toURI().toString(); + + assertTrue(info.getDependencyResourceNames().contains(expectedDep1Name)); + assertTrue(info.getDependencyResourceNames().contains(expectedDep2Name)); + } + +} diff -r 186115da601f -r 1936ad067842 launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/PluginConfigurationParserTest.java Mon Jan 28 16:03:37 2013 -0500 @@ -0,0 +1,135 @@ +/* + * Copyright 2013 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.redhat.thermostat.launcher.internal.PluginConfiguration.CommandExtensions; +import com.redhat.thermostat.launcher.internal.PluginConfiguration.NewCommand; + +public class PluginConfigurationParserTest { + + @Test(expected = PluginConfigurationParseException.class) + public void testEmptyConfigurationThrowsException() throws UnsupportedEncodingException { + String config = "\n"; + PluginConfigurationParser parser = new PluginConfigurationParser(); + parser.parse(new ByteArrayInputStream(config.getBytes("UTF-8"))); + fail("should not reach here"); + } + + @Test + public void testMinimalConfiguration() throws UnsupportedEncodingException { + PluginConfigurationParser parser = new PluginConfigurationParser(); + String config = "" + + "\n" + + "\n" + + ""; + PluginConfiguration result = parser.parse(new ByteArrayInputStream(config.getBytes("UTF-8"))); + + assertEquals(0, result.getExtendedCommands().size()); + assertEquals(0, result.getNewCommands().size()); + } + + @Test + public void testConfigurationThatExtendsExistingCommand() throws UnsupportedEncodingException { + String config = "\n" + + "\n" + + " \n" + + " \n" + + " test\n" + + " foo,bar,baz,\n" + + " thermostat-foo\n" + + " \n" + + " \n" + + ""; + + PluginConfiguration result = new PluginConfigurationParser() + .parse(new ByteArrayInputStream(config.getBytes("UTF-8"))); + + assertEquals(0, result.getNewCommands().size()); + + List extensions = result.getExtendedCommands(); + assertEquals(1, extensions.size()); + + CommandExtensions first = extensions.get(0); + assertEquals("test", first.getCommandName()); + assertEquals(Arrays.asList("foo", "bar", "baz"), first.getAdditionalBundles()); + assertEquals(Arrays.asList("thermostat-foo"), first.getDepenedencyBundles()); + } + + @Test + public void testConfigurationThatAddsNewCommand() throws UnsupportedEncodingException { + String config = "\n" + + "\n" + + " \n" + + " \n" + + " test\n" + + " usage: test\n" + + " description\n" + + " foo,bar,baz,\n" + + " thermostat-foo\n" + + " \n" + + " \n" + + ""; + + PluginConfiguration result = new PluginConfigurationParser() + .parse(new ByteArrayInputStream(config.getBytes("UTF-8"))); + + List extensions = result.getExtendedCommands(); + assertEquals(0, extensions.size()); + + List newCommands = result.getNewCommands(); + assertEquals(1, newCommands.size()); + + NewCommand newCommand = newCommands.get(0); + assertEquals("test", newCommand.getCommandName()); + assertEquals("usage: test", newCommand.getUsage()); + assertEquals("description", newCommand.getDescription()); + assertEquals(null, newCommand.getOptions()); + assertEquals(Arrays.asList("foo", "bar", "baz"), newCommand.getAdditionalBundles()); + assertEquals(Arrays.asList("thermostat-foo"), newCommand.getCoreDepenedencyBundles()); + } + +}