changeset 648:8cc8dea8b9d6

Give each command its own config file Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-September/003393.html
author Jon VanAlten <vanaltj@gmail.com>
date Tue, 02 Oct 2012 17:37:10 -0400
parents 654fc29f96f2
children 73c182a706fc
files bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleProperties.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundlePropertiesTest.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java common/core/src/main/java/com/redhat/thermostat/common/Configuration.java distribution/config/bundles.properties distribution/config/commands/agent.properties distribution/config/commands/dump-heap.properties distribution/config/commands/find-objects.properties distribution/config/commands/find-root.properties distribution/config/commands/gui.properties distribution/config/commands/list-heap-dumps.properties distribution/config/commands/object-info.properties distribution/config/commands/ping.properties distribution/config/commands/save-heap-dump-to-file.properties distribution/config/commands/service.properties distribution/config/commands/show-heap-histogram.properties distribution/config/commands/storage.properties distribution/pom.xml launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfo.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoSource.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoTest.java main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java
diffstat 27 files changed, 607 insertions(+), 443 deletions(-) [+]
line wrap: on
line diff
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java	Tue Oct 02 17:37:10 2012 -0400
@@ -43,12 +43,15 @@
 import org.osgi.framework.launch.Framework;
 
 import com.redhat.thermostat.bundles.impl.BundleLoader;
+import com.redhat.thermostat.common.Configuration;
 
 
 public abstract class OSGiRegistry {
 
     public abstract void setPrintOSGiInfo(boolean printOSGiInfo);
 
+    public abstract void setCommandBundleDependencies(String commandName, List<String> resourceNames);
+
     public abstract void addBundlesFor(String commandName) throws BundleException, IOException;
 
     public static void preLoadBundles(Framework framework, List<String> bundleLocations,
@@ -57,4 +60,6 @@
         loader.installAndStartBundles(framework, bundleLocations);
     }
 
+    public abstract Configuration getConfiguration();
+
 }
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleProperties.java	Fri Sep 28 11:35:11 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright 2012 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
- * <http://www.gnu.org/licenses/>.
- *
- * 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.bundles.impl;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-
-public class BundleProperties {
-
-    private Map<String, List<String>> bundleDependencies;
-
-    BundleProperties(String thermostatHome) throws FileNotFoundException, IOException {
-        bundleDependencies = new HashMap<>();
-        File bundlePropFile = new File(thermostatHome + File.separator + "etc", "bundles.properties");
-        Properties bundleProps = new Properties();
-        bundleProps.load(new FileReader(bundlePropFile));
-        for (Entry<Object,Object> entry: bundleProps.entrySet()) {
-            String libRoot = thermostatHome + File.separator + "libs";
-            String group = (String) entry.getKey();
-            List<String> resourceNames = Arrays.asList(((String)entry.getValue()).split(","));
-            List<String> paths = new ArrayList<>(resourceNames.size());
-            for (String value: resourceNames) {
-                File file = new File(libRoot, value.trim());
-                String path = file.toURI().toString();
-                if (!file.exists()) {
-                    throw new FileNotFoundException("Bundle " + path + " required by " +
-                            group + " command does not exist in the filesystem.");
-                }
-                paths.add(path);
-            }
-            bundleDependencies.put(group, paths);
-        }
-
-    }
-
-    public List<String> getDependencyResourceNamesFor(String group) {
-        List<String> deps = bundleDependencies.get(group);
-        if (deps == null) {
-            deps = new ArrayList<String>();
-            bundleDependencies.put(group, deps);
-        }
-        return deps;
-    }
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java	Tue Oct 02 17:37:10 2012 -0400
@@ -54,18 +54,16 @@
 
 public class OSGiRegistryImpl extends OSGiRegistry {
 
+    private Map<String, List<String>> needed;
     private Map<String, Bundle> loaded;
     private Configuration configuration;
-    private String thermostatHome;
-    private BundleProperties bundleProperties;
     private BundleLoader loader;
 
     OSGiRegistryImpl(Configuration configuration) throws ConfigurationException, FileNotFoundException, IOException {
         initLoadedBundles();
-        thermostatHome = configuration.getThermostatHome();
-        bundleProperties = new BundleProperties(thermostatHome);
         this.configuration = configuration;
         loader = new BundleLoader(configuration.getPrintOSGiInfo());
+        needed = new HashMap<>();
     }
 
     private void initLoadedBundles() {
@@ -83,15 +81,22 @@
     }
 
     @Override
+    public void setCommandBundleDependencies(String commandName, List<String> resourceNames) {
+        needed.put(commandName, resourceNames);
+    }
+
+    @Override
     public void addBundlesFor(String commandName) throws BundleException, IOException {
         if (configuration.getPrintOSGiInfo()) {
             System.out.println("Loading additional bundles for: " + commandName);
         }
-        List<String> requiredBundles = bundleProperties.getDependencyResourceNamesFor(commandName);
+        List<String> requiredBundles = needed.get(commandName);
         List<String> bundlesToLoad = new ArrayList<>();
-        for (String resource : requiredBundles) {
-            if (!isBundleActive(resource)) {
-                bundlesToLoad.add(resource);
+        if (requiredBundles != null) {
+            for (String resource : requiredBundles) {
+                if (!isBundleActive(resource)) {
+                    bundlesToLoad.add(resource);
+                }
             }
         }
         Framework framework = getFramework(this.getClass());
@@ -109,4 +114,9 @@
     private Framework getFramework(Class<?> cls) {
         return (Framework) FrameworkUtil.getBundle(cls).getBundleContext().getBundle(0);
     }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configuration;
+    }
 }
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundlePropertiesTest.java	Fri Sep 28 11:35:11 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +0,0 @@
-/*
- * Copyright 2012 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
- * <http://www.gnu.org/licenses/>.
- *
- * 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.bundles.impl;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Properties;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-public class BundlePropertiesTest {
-
-    private Path tempThermostatHome;
-
-    private File tempEtc;
-    private File tempPropsFile;
-
-    private Path someJarName1;
-    private Path someJarName2;
-
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
-    @Before
-    public void setUp() throws IOException {
-
-        tempThermostatHome = Files.createTempDirectory("test");
-        tempThermostatHome.toFile().deleteOnExit();
-        System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString());
-        
-        tempEtc = new File(tempThermostatHome.toFile(), "etc");
-        tempEtc.mkdirs();
-        tempEtc.deleteOnExit();
-        
-        File tempProps = new File(tempEtc, "bundles.properties");
-        tempProps.createNewFile();
-        tempProps.deleteOnExit();
-        
-        File 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();
-    }
-
-    private Properties getSingleBundleProperties() {
-        Properties props = new Properties();
-        props.setProperty("foo", someJarName1.getFileName().toString());
-        return props;
-    }
-
-    private Properties getMultipleBundleProperties() {
-        Properties props = new Properties();
-        props.setProperty("foo", someJarName1.getFileName() + "," + someJarName2.getFileName());
-        return props;
-    }
-
-    private Properties getPropertiesWithMultipleCommands() {
-        Properties props = getSingleBundleProperties();
-        props.setProperty("bar", someJarName2.getFileName().toString());
-        return props;
-    }
-
-    private void writeProperties(Properties props) {
-        tempPropsFile = new File(tempEtc, "bundles.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);
-        }
-    }
-
-    private void deletePropertiesFile() {
-        if (tempPropsFile.exists()) {
-            tempPropsFile.delete();
-        }
-    }
-
-    private String resolvedJar(Path jar) {
-        return "file:" + jar.toString();
-    }
-
-    @Test
-    public void testSingleReferencedJarPresent() throws FileNotFoundException, IOException {
-        Properties props = getSingleBundleProperties();
-        writeProperties(props);
-        BundleProperties bundles = new BundleProperties(tempThermostatHome.toString());
-        List<String> jarNames = bundles.getDependencyResourceNamesFor("foo");
-        assertEquals(1, jarNames.size());
-        assertTrue(jarNames.contains(resolvedJar(someJarName1)));
-        deletePropertiesFile();
-    }
-
-    @Test
-    public void testMultipleReferencedJarPresent() throws FileNotFoundException, IOException {
-        Properties props = getMultipleBundleProperties();
-        writeProperties(props);
-        BundleProperties bundles = new BundleProperties(tempThermostatHome.toString());
-        List<String> jarNames = bundles.getDependencyResourceNamesFor("foo");
-        assertEquals(2, jarNames.size());
-        assertTrue(jarNames.contains(resolvedJar(someJarName1)));
-        assertTrue(jarNames.contains(resolvedJar(someJarName2)));
-        deletePropertiesFile();
-    }
-
-    @Test
-    public void testSomeReferencedJarMissing() throws IOException {
-        thrown.expect(FileNotFoundException.class);
-        Properties props = getMultipleBundleProperties();
-        props.setProperty("baz", "thisjar_noexist.jar");
-        writeProperties(props);
-        @SuppressWarnings("unused")
-        BundleProperties bundles = new BundleProperties(tempThermostatHome.toString());
-    }
-
-    @Test
-    public void testWithMultipleCommands() throws FileNotFoundException, IOException {
-        Properties props = getPropertiesWithMultipleCommands();
-        writeProperties(props);
-        BundleProperties bundles = new BundleProperties(tempThermostatHome.toString());
-        List<String> jarNames = bundles.getDependencyResourceNamesFor("foo");
-        assertEquals(1, jarNames.size());
-        assertTrue(jarNames.contains(resolvedJar(someJarName1)));
-        jarNames = bundles.getDependencyResourceNamesFor("bar");
-        assertEquals(1, jarNames.size());
-        assertTrue(jarNames.contains(resolvedJar(someJarName2)));
-        deletePropertiesFile();
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java	Tue Oct 02 17:37:10 2012 -0400
@@ -37,7 +37,6 @@
 package com.redhat.thermostat.bundles.impl;
 
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -46,7 +45,6 @@
 import static org.powermock.api.mockito.PowerMockito.whenNew;
 
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -76,7 +74,6 @@
     private List<String> bundleLocs;
 
     private BundleLoader loader;
-    private BundleProperties bundleProps;
     private Configuration conf;
     
     @Before
@@ -94,12 +91,7 @@
         when(b3.getLocation()).thenReturn(jar3Name);
         when(b3.getState()).thenReturn(Bundle.ACTIVE);
         List<Bundle> installed = Arrays.asList(b1, b2, b3);
-        
-        bundleProps = mock(BundleProperties.class);
-        when(bundleProps.getDependencyResourceNamesFor(eq(cmdName))).
-                thenReturn(bundleLocs);
-        whenNew(BundleProperties.class).withParameterTypes(String.class).
-                withArguments(anyString()).thenReturn(bundleProps);
+
         loader = mock(BundleLoader.class);
         when(loader.installAndStartBundles(any(Framework.class), eq(bundleLocs))).
                 thenReturn(installed);
@@ -129,6 +121,7 @@
         when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
 
         OSGiRegistryImpl registry = new OSGiRegistryImpl(conf);
+        registry.setCommandBundleDependencies(cmdName, bundleLocs);
         registry.addBundlesFor(cmdName);
         verify(loader).installAndStartBundles(any(Framework.class), eq(locationsNeeded));
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/Configuration.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Configuration.java	Tue Oct 02 17:37:10 2012 -0400
@@ -52,21 +52,21 @@
     private final String home;
     private boolean printOSGiInfo;
 
-    public Configuration() {
+    public Configuration() throws ConfigurationException {
         // allow this to be specified also as a property, especially for
         // tests, this overrides the env setting
         String home = System.getProperty("THERMOSTAT_HOME");
         if (home == null) {
             home = System.getenv("THERMOSTAT_HOME");
         }
+        if (home == null) {
+            throw new ConfigurationException("THERMOSTAT_HOME is not defined as either Java property or environment variable.  Cannot proceed.");
+        }
         this.home = home;
         printOSGiInfo = false;
     }
 
-    public String getThermostatHome() throws ConfigurationException {
-        if (home == null) {
-            throw new ConfigurationException("THERMOSTAT_HOME not defined...");
-        }
+    public String getThermostatHome() {
         return home;
     }
 
--- a/distribution/config/bundles.properties	Fri Sep 28 11:35:11 2012 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +0,0 @@
-launcher = thermostat-launcher-@project.version@.jar, \
-           thermostat-keyring-@project.version@.jar, \
-           thermostat-common-core-@project.version@.jar, \
-           thermostat-osgi-process-handler-@project.version@.jar, \
-           thermostat-tools-@project.version@.jar, \
-           thermostat-agent-core-@project.version@.jar
-
-gui = thermostat-common-core-@project.version@.jar, \
-      thermostat-common-command-@project.version@.jar, \
-      thermostat-client-core-@project.version@.jar, \
-      thermostat-client-command-@project.version@.jar, \
-      thermostat-client-heapdumper-@project.version@.jar, \
-      thermostat-client-killvm-@project.version@.jar, \
-      thermostat-client-vmclassstat-@project.version@.jar, \
-      thermostat-osgi-living-vm-filter-@project.version@.jar, \
-      thermostat-osgi-memory-stats-panel-@project.version@.jar, \
-      thermostat-swing-components-@project.version@.jar, \
-      thermostat-laf-@project.version@.jar, \
-      thermostat-thread-collector-@project.version@.jar, \
-      thermostat-thread-client-swing-@project.version@.jar, \
-      thermostat-thread-client-controllers-@project.version@.jar, \
-      thermostat-thread-client-common-@project.version@.jar, \
-      thermostat-osgi-process-handler-@project.version@.jar, \
-      thermostat-client-command-@project.version@.jar, \
-      thermostat-client-command-@project.version@.jar, \
-      thermostat-agent-command-@project.version@.jar
-
-service = thermostat-agent-core-@project.version@.jar, \
-          thermostat-osgi-process-handler-@project.version@.jar, \
-          thermostat-common-core-@project.version@.jar, \
-          thermostat-common-command-@project.version@.jar, \
-          thermostat-agent-command-@project.version@.jar, \
-          thermostat-agent-cli-@project.version@.jar
-
-agent = thermostat-agent-core-@project.version@.jar, \
-        thermostat-osgi-process-handler-@project.version@.jar, \
-        thermostat-common-core-@project.version@.jar, \
-        thermostat-agent-cli-@project.version@.jar, \
-        thermostat-common-command-@project.version@.jar, \
-        thermostat-agent-command-@project.version@.jar, \
-        thermostat-agent-heapdumper-@project.version@.jar, \
-        thermostat-agent-killvm-@project.version@.jar, \
-        thermostat-thread-collector-@project.version@.jar, \
-        thermostat-thread-harvester-@project.version@.jar, \
-        thermostat-client-command-@project.version@.jar
-
-storage = thermostat-agent-core-@project.version@.jar, \
-          thermostat-osgi-process-handler-@project.version@.jar, \
-          thermostat-common-core-@project.version@.jar, \
-          thermostat-agent-cli-@project.version@.jar, \
-          thermostat-common-command-@project.version@.jar, \
-          thermostat-agent-command-@project.version@.jar
-
-dump-heap = thermostat-common-core-@project.version@.jar, \
-            thermostat-common-command-@project.version@.jar, \
-            thermostat-client-command-@project.version@.jar, \
-            thermostat-client-core-@project.version@.jar, \
-            thermostat-client-heapdumper-@project.version@.jar, \
-            thermostat-swing-components-@project.version@.jar, \
-            thermostat-laf-@project.version@.jar
-
-object-info = thermostat-common-core-@project.version@.jar, \
-              thermostat-common-command-@project.version@.jar, \
-              thermostat-client-command-@project.version@.jar, \
-              thermostat-client-core-@project.version@.jar, \
-              thermostat-client-heapdumper-@project.version@.jar, \
-              thermostat-swing-components-@project.version@.jar, \
-              thermostat-laf-@project.version@.jar
-
-find-root = thermostat-common-core-@project.version@.jar, \
-            thermostat-common-command-@project.version@.jar, \
-            thermostat-client-command-@project.version@.jar, \
-            thermostat-client-core-@project.version@.jar, \
-            thermostat-client-heapdumper-@project.version@.jar, \
-            thermostat-swing-components-@project.version@.jar, \
-            thermostat-laf-@project.version@.jar
-
-find-objects = thermostat-common-core-@project.version@.jar, \
-               thermostat-common-command-@project.version@.jar, \
-               thermostat-client-command-@project.version@.jar, \
-               thermostat-client-core-@project.version@.jar, \
-               thermostat-client-heapdumper-@project.version@.jar, \
-               thermostat-swing-components-@project.version@.jar, \
-               thermostat-laf-@project.version@.jar
-
-list-heap-dumps = thermostat-common-core-@project.version@.jar, \
-                  thermostat-common-command-@project.version@.jar, \
-                  thermostat-client-command-@project.version@.jar, \
-                  thermostat-client-core-@project.version@.jar, \
-                  thermostat-client-heapdumper-@project.version@.jar, \
-                  thermostat-swing-components-@project.version@.jar, \
-                  thermostat-laf-@project.version@.jar
-
-save-heap-dump-to-file = thermostat-common-core-@project.version@.jar, \
-                         thermostat-common-command-@project.version@.jar, \
-                         thermostat-client-command-@project.version@.jar, \
-                         thermostat-client-core-@project.version@.jar, \
-                         thermostat-client-heapdumper-@project.version@.jar, \
-                         thermostat-swing-components-@project.version@.jar, \
-                         thermostat-laf-@project.version@.jar
-
-show-heap-histogram = thermostat-common-core-@project.version@.jar, \
-                      thermostat-common-command-@project.version@.jar, \
-                      thermostat-client-command-@project.version@.jar, \
-                      thermostat-client-core-@project.version@.jar, \
-                      thermostat-client-heapdumper-@project.version@.jar, \
-                      thermostat-swing-components-@project.version@.jar, \
-                      thermostat-laf-@project.version@.jar
-
-ping = thermostat-common-command-@project.version@.jar, \
-       thermostat-client-command-@project.version@.jar
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/agent.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,10 @@
+bundles = thermostat-agent-core-@project.version@.jar, \
+          thermostat-osgi-process-handler-@project.version@.jar, \
+          thermostat-common-core-@project.version@.jar, \
+          thermostat-agent-cli-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-agent-command-@project.version@.jar, \
+          thermostat-agent-heapdumper-@project.version@.jar, \
+          thermostat-agent-killvm-@project.version@.jar, \
+          thermostat-thread-collector-@project.version@.jar, \
+          thermostat-thread-harvester-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/dump-heap.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/find-objects.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/find-root.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/gui.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,18 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-client-killvm-@project.version@.jar, \
+          thermostat-client-vmclassstat-@project.version@.jar, \
+          thermostat-osgi-living-vm-filter-@project.version@.jar, \
+          thermostat-osgi-memory-stats-panel-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar, \
+          thermostat-thread-collector-@project.version@.jar, \
+          thermostat-thread-client-swing-@project.version@.jar, \
+          thermostat-thread-client-controllers-@project.version@.jar, \
+          thermostat-thread-client-common-@project.version@.jar, \
+          thermostat-osgi-process-handler-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/list-heap-dumps.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/object-info.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/ping.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,2 @@
+bundles = thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/save-heap-dump-to-file.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/service.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,6 @@
+bundles = thermostat-agent-core-@project.version@.jar, \
+          thermostat-osgi-process-handler-@project.version@.jar, \
+          thermostat-common-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-agent-command-@project.version@.jar, \
+          thermostat-agent-cli-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/show-heap-histogram.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,7 @@
+bundles = thermostat-common-core-@project.version@.jar, \
+          thermostat-client-core-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-client-command-@project.version@.jar, \
+          thermostat-client-heapdumper-@project.version@.jar, \
+          thermostat-swing-components-@project.version@.jar, \
+          thermostat-laf-@project.version@.jar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/storage.properties	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,6 @@
+bundles = thermostat-agent-core-@project.version@.jar, \
+          thermostat-osgi-process-handler-@project.version@.jar, \
+          thermostat-common-core-@project.version@.jar, \
+          thermostat-agent-cli-@project.version@.jar, \
+          thermostat-common-command-@project.version@.jar, \
+          thermostat-agent-command-@project.version@.jar
--- a/distribution/pom.xml	Fri Sep 28 11:35:11 2012 -0400
+++ b/distribution/pom.xml	Tue Oct 02 17:37:10 2012 -0400
@@ -129,7 +129,7 @@
                   <includes>
                     <include>logging.properties</include>
                     <include>osgi-export.properties</include>
-                    <include>bundles.properties</include>
+                    <include>commands/*.properties</include>
                   </includes>
                 </resource>
                 <resource>
@@ -166,6 +166,7 @@
             <configuration>
               <target>
                 <mkdir dir="${project.build.directory}/etc" />
+                <mkdir dir="${project.build.directory}/etc/commands" />
                 <mkdir dir="${project.build.directory}/storage" />
                 <mkdir dir="${project.build.directory}/storage/db" />
                 <mkdir dir="${project.build.directory}/storage/logs" />
@@ -181,7 +182,6 @@
                       todir="${project.build.directory}/libs/native" />
                 <copy file="${main.basedir}/agent/core/target/libHostNameWrapper.so"
                       todir="${project.build.directory}/libs/native" />
-                      
               </target>
             </configuration>
             <goals>
@@ -192,37 +192,33 @@
       </plugin>
     </plugins>
     <pluginManagement>
-    	<plugins>
-    		<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
-    		<plugin>
-    			<groupId>org.eclipse.m2e</groupId>
-    			<artifactId>lifecycle-mapping</artifactId>
-    			<version>1.0.0</version>
-    			<configuration>
-    				<lifecycleMappingMetadata>
-    					<pluginExecutions>
-    						<pluginExecution>
-    							<pluginExecutionFilter>
-    								<groupId>
-    									org.apache.maven.plugins
-    								</groupId>
-    								<artifactId>
-    									maven-dependency-plugin
-    								</artifactId>
-    								<versionRange>[2.4,)</versionRange>
-    								<goals>
-    									<goal>copy-dependencies</goal>
-    								</goals>
-    							</pluginExecutionFilter>
-    							<action>
-    								<ignore></ignore>
-    							</action>
-    						</pluginExecution>
-    					</pluginExecutions>
-    				</lifecycleMappingMetadata>
-    			</configuration>
-    		</plugin>
-    	</plugins>
+      <plugins>
+        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+        <plugin>
+          <groupId>org.eclipse.m2e</groupId>
+          <artifactId>lifecycle-mapping</artifactId>
+          <version>1.0.0</version>
+          <configuration>
+            <lifecycleMappingMetadata>
+              <pluginExecutions>
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-dependency-plugin</artifactId>
+                    <versionRange>[2.4,)</versionRange>
+                    <goals>
+                      <goal>copy-dependencies</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore></ignore>
+                  </action>
+                </pluginExecution>
+              </pluginExecutions>
+            </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
     </pluginManagement>
   </build>
   <dependencies>
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Tue Oct 02 17:37:10 2012 -0400
@@ -64,6 +64,8 @@
             
             ServiceReference reference = context.getServiceReference(OSGiRegistry.class);
             OSGiRegistry bundleService = (OSGiRegistry) context.getService(reference);
+            CommandInfoSource commands = new CommandInfoSource(bundleService.getConfiguration().getThermostatHome());
+            informRegistryAboutCommandDependencies(commands, bundleService);
             LauncherImpl launcher = new LauncherImpl(context,
                     new CommandContextFactory(context), bundleService);
             launcherServiceRegistration = context.registerService(Launcher.class.getName(), launcher, null);
@@ -71,6 +73,13 @@
         
     }
 
+    private void informRegistryAboutCommandDependencies(CommandInfoSource commands, OSGiRegistry bundleService) {
+        for (CommandInfo info : commands.getCommandInfos()) {
+            bundleService.setCommandBundleDependencies(info.getName(),
+                info.getDependencyResourceNames());
+        }
+    }
+
     @SuppressWarnings("rawtypes")
     private ServiceRegistration launcherServiceRegistration;
     private MultipleServiceTracker tracker;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfo.java	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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;
+
+
+public class CommandInfo {
+
+    private static final Logger logger = Logger.getLogger(CommandInfoSource.class.getSimpleName());
+    private static final String PROPERTY_BUNDLES = "bundles";
+
+    private String name;
+    private List<String> dependencies;
+
+    CommandInfo(String name, Properties properties, String thermostatHome) {
+        this.name = name;
+        for (Entry<Object,Object> entry: properties.entrySet()) {
+            String key = (String) entry.getKey();
+            if (key.equals(PROPERTY_BUNDLES)) {
+                learnDependencies(entry, thermostatHome);
+            }
+            
+        }
+    }
+
+    private void learnDependencies(Entry<Object, Object> bundlesEntry, String thermostatHome) {
+        String libRoot = thermostatHome + File.separator + "libs";
+        List<String> resourceNames = Arrays.asList(((String)bundlesEntry.getValue()).split(","));
+        dependencies = new ArrayList<>(resourceNames.size());
+        for (String value: resourceNames) {
+            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);
+            }
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    List<String> getDependencyResourceNames() {
+        return dependencies;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/CommandInfoSource.java	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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;
+
+public class CommandInfoSource {
+
+    private static final Logger logger = Logger.getLogger(CommandInfoSource.class.getSimpleName());
+    private Map<String, CommandInfo> commands;
+
+    CommandInfoSource(String thermostatHome) {
+        commands = new HashMap<>();
+        final File dir = new File(thermostatHome + File.separator + "etc", "commands");
+        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 CommandInfo(commandName, commandProps, thermostatHome));
+            }
+        } 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);
+    }
+
+    public CommandInfo getCommandInfo(String name) {
+        return commands.get(name);
+    }
+
+    public Collection<CommandInfo> getCommandInfos() {
+        return commands.values();
+    }
+
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Tue Oct 02 17:37:10 2012 -0400
@@ -46,6 +46,7 @@
 import static org.powermock.api.mockito.PowerMockito.verifyNew;
 import static org.powermock.api.mockito.PowerMockito.whenNew;
 
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Map;
@@ -61,6 +62,7 @@
 import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.redhat.thermostat.bundles.OSGiRegistry;
+import com.redhat.thermostat.common.Configuration;
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.cli.Command;
@@ -100,6 +102,16 @@
         when(context.getService(helpCommandReference)).thenReturn(helpCommand);
         when(context.getServiceReferences(Command.class.getName(), null)).thenReturn(new ServiceReference[] {helpCommandReference});
 
+        Configuration config = mock(Configuration.class);
+        when(config.getThermostatHome()).thenReturn("");
+        when(registryService.getConfiguration()).thenReturn(config);
+
+        CommandInfoSource commands = mock(CommandInfoSource.class);
+        when(commands.getCommandInfos()).thenReturn(new ArrayList<CommandInfo>());
+        whenNew(CommandInfoSource.class).
+                withParameterTypes(String.class).
+                withArguments(isA(String.class)).thenReturn(commands);
+
         tracker = mock(MultipleServiceTracker.class);
         whenNew(MultipleServiceTracker.class).
                 withParameterTypes(BundleContext.class, Class[].class, Action.class).
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoSourceTest.java	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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.launcher.internal.CommandInfoSource;
+
+public class CommandInfoSourceTest {
+
+    private Path tempThermostatHome;
+
+    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());
+        
+        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() {
+        CommandInfoSource bundles = new CommandInfoSource(tempThermostatHome.toString());
+        CommandInfo info = bundles.getCommandInfo("foo");
+        assertNotNull(info);
+        assertEquals("foo", info.getName());
+    }
+
+    @Test
+    public void testGetCommandInfos() {
+        CommandInfoSource bundles = new CommandInfoSource(tempThermostatHome.toString());
+        Collection<CommandInfo> infos = bundles.getCommandInfos();
+        assertNotNull(infos);
+        assertEquals(1, infos.size());
+        CommandInfo info = infos.iterator().next();
+        assertNotNull(info);
+        assertEquals("foo", info.getName());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/CommandInfoTest.java	Tue Oct 02 17:37:10 2012 -0400
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2012 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
+ * <http://www.gnu.org/licenses/>.
+ *
+ * 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.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CommandInfoTest {
+
+    private Path tempThermostatHome, someJarName1, someJarName2, missingJarName;
+
+    @Before
+    public void setUp() throws IOException {
+        tempThermostatHome = Files.createTempDirectory("test");
+        tempThermostatHome.toFile().deleteOnExit();
+        System.setProperty("THERMOSTAT_HOME", tempThermostatHome.toString());
+
+        File 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";
+        CommandInfo info = new CommandInfo(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";
+        CommandInfo info = new CommandInfo(name, props, tempThermostatHome.toString());
+
+        List<String> 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";
+        CommandInfo info = new CommandInfo(name, props, tempThermostatHome.toString());
+
+        List<String> 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";
+        CommandInfo info = new CommandInfo(name, props, tempThermostatHome.toString());
+
+        List<String> resources = info.getDependencyResourceNames();
+        assertEquals(0, resources.size());
+        assertFalse(resources.contains(resolvedJar(missingJarName)));
+    }
+}
--- a/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Fri Sep 28 11:35:11 2012 -0400
+++ b/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Tue Oct 02 17:37:10 2012 -0400
@@ -77,11 +77,7 @@
 
     public FrameworkProvider(Configuration config) {
         printOSGiInfo = config.getPrintOSGiInfo();
-        try {
-            this.thermostatHome = config.getThermostatHome();
-        } catch (ConfigurationException e) {
-            throw new RuntimeException("Cannot initialize frameworks without valid ThermostatHome.", e);
-        }
+        this.thermostatHome = config.getThermostatHome();
     }
 
     // This is our ticket into OSGi land. Unfortunately, we to use a bit of reflection here.