changeset 906:b1b81446c892

Merge bundles/launcher plug-ins. Here is a patch which merges classes which used to be in the bundles plug-in into the launcher plug-in. I don't see why this splitting is useful any longer. Now we have the "main" jar which fires up the framework and uses the launcher plug-in to do the osgi-based launching. This is a first step to clean up dependencies amongst bundles which should eventually lead to cleaner dep-trees and common-core not depend on storage-core any longer, having this c.r.t.test package removed from common-core etc. ThermostatTest was also largely commented out. The only non-comment part was the setup routine. A few basic tests have been reinstated. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005093.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Thu, 10 Jan 2013 16:16:29 +0100
parents 6da66da9e8ac
children 2930e0c260cc
files bundles/pom.xml bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/Activator.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleLoader.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java bundles/src/test/java/com/redhat/thermostat/bundles/OSGiRegistryTest.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundleLoaderTest.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java distribution/pom.xml launcher/pom.xml launcher/src/main/java/com/redhat/thermostat/launcher/BundleManager.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleLoader.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleManagerImpl.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java launcher/src/test/java/com/redhat/thermostat/launcher/BundleManagerTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleLoaderTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleManagerImplTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java main/pom.xml main/src/main/java/com/redhat/thermostat/main/Thermostat.java main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory pom.xml
diffstat 27 files changed, 824 insertions(+), 893 deletions(-) [+]
line wrap: on
line diff
--- a/bundles/pom.xml	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-
- 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.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.redhat.thermostat</groupId>
-    <artifactId>thermostat</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>thermostat-bundles</artifactId>
-  <packaging>bundle</packaging>
-
-  <name>Service providing entry point to OSGi-land</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-api-mockito</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-module-junit4</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.jboss.netty</groupId>
-      <artifactId>netty</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-core</artifactId>
-      <version>${project.version}</version>
-      <type>jar</type>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.bundles.impl.Activator</Bundle-Activator>
-            <Bundle-SymbolicName>com.redhat.thermostat.bundles.core</Bundle-SymbolicName>
-            <Export-Package>
-              com.redhat.thermostat.bundles
-            </Export-Package>
-            <Private-Package>
-              com.redhat.thermostat.bundles.impl
-            </Private-Package>
-            <!-- Do not autogenerate uses clauses in Manifests -->
-            <_nouses>true</_nouses>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-</project>
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.osgi.framework.BundleException;
-import org.osgi.framework.launch.Framework;
-
-import com.redhat.thermostat.bundles.impl.BundleLoader;
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-/**
- * A Service that provides features to load bundles for given command names.
- */
-public abstract class OSGiRegistry {
-
-    public abstract void setPrintOSGiInfo(boolean printOSGiInfo);
-
-    public abstract void setCommandInfoSource(CommandInfoSource source);
-
-    public abstract void addBundlesFor(String commandName) throws BundleException, CommandInfoNotFoundException, IOException;
-
-    public static void preLoadBundles(Framework framework, List<String> bundleLocations,
-            boolean printOSGiInfo) throws BundleException {
-        BundleLoader loader = new BundleLoader(printOSGiInfo);
-        loader.installAndStartBundles(framework, bundleLocations);
-    }
-
-    public abstract Configuration getConfiguration();
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/Activator.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +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 org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-import com.redhat.thermostat.bundles.OSGiRegistry;
-import com.redhat.thermostat.common.Configuration;
-
-public class Activator implements BundleActivator {
-
-    ServiceRegistration reg;
-
-    @Override
-    public void start(BundleContext context) throws Exception {
-        OSGiRegistryImpl bundleRegistry = new OSGiRegistryImpl(new Configuration());
-        reg = context.registerService(OSGiRegistry.class.getName(), bundleRegistry, null);
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (reg != null) {
-            reg.unregister();
-            reg = null;
-        }
-    }
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleLoader.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +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.util.ArrayList;
-import java.util.List;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.launch.Framework;
-
-public class BundleLoader {
-
-    private boolean printOSGiInfo = false;
-
-    BundleLoader() {
-        this(false);
-    }
-
-    public BundleLoader(boolean printOSGiInfo) {
-        setPrintOSGiInfo(printOSGiInfo);
-    }
-
-    public void setPrintOSGiInfo(boolean printOSGiInfo) {
-        this.printOSGiInfo = printOSGiInfo;
-    }
-
-    public List<Bundle> installAndStartBundles(Framework framework,
-            List<String>bundleLocations) throws BundleException {
-        List<Bundle> bundles = new ArrayList<>();
-        BundleContext ctx = framework.getBundleContext();
-        for (String location : bundleLocations) {
-            Bundle bundle = ctx.installBundle(location);
-            if (printOSGiInfo) {
-                System.out.println("BundleLoader: installed bundle: \"" + 
-                        location + "\" as id " + bundle.getBundleId());
-            }
-            bundles.add(bundle);
-        }
-        startBundles(bundles);
-        return bundles;
-    }
-
-    private void startBundles(List<Bundle> bundles) throws BundleException {
-        for (Bundle bundle : bundles) {
-
-            if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
-                if (printOSGiInfo) {
-                    System.out.println("BundleLoader: bundle \"" + bundle.getBundleId() + "\" is a fragment; not starting it");
-                }
-                continue;
-            }
-
-            if (printOSGiInfo) {
-                System.out.println("BundleLoader: starting bundle: \"" + bundle.getBundleId() + "\"");
-            }
-            // We don't want for the framework to set the auto-start bit. Thus, passing
-            // START_TRANSIENT explicitly
-            bundle.start(Bundle.START_TRANSIENT);
-        }
-    }
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +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 com.redhat.thermostat.bundles.OSGiRegistry;
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.ConfigurationException;
-import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-
-public class OSGiRegistryImpl extends OSGiRegistry {
-
-    private CommandInfoSource commandInfos;
-    private Map<String, Bundle> loaded;
-    private Configuration configuration;
-    private BundleLoader loader;
-
-    OSGiRegistryImpl(Configuration configuration) throws ConfigurationException, FileNotFoundException, IOException {
-        initLoadedBundles();
-        this.configuration = configuration;
-        loader = new BundleLoader(configuration.getPrintOSGiInfo());
-    }
-
-    private void initLoadedBundles() {
-        loaded = new HashMap<>();
-        Framework framework = getFramework(this.getClass());
-        for (Bundle bundle: framework.getBundleContext().getBundles()) {
-            loaded.put(bundle.getLocation(), bundle);
-        }
-    }
-
-    @Override
-    public void setPrintOSGiInfo(boolean printOSGiInfo) {
-        configuration.setPrintOSGiInfo(printOSGiInfo);
-        loader.setPrintOSGiInfo(printOSGiInfo);
-    }
-
-    @Override
-    public void setCommandInfoSource(CommandInfoSource source) {
-        this.commandInfos = source;
-    }
-
-    @Override
-    public void addBundlesFor(String commandName) throws BundleException, IOException, CommandInfoNotFoundException {
-        if (configuration.getPrintOSGiInfo()) {
-            System.out.println("Loading additional bundles for: " + commandName);
-        }
-        List<String> requiredBundles = commandInfos.getCommandInfo(commandName).getDependencyResourceNames();
-        List<String> bundlesToLoad = new ArrayList<>();
-        if (requiredBundles != null) {
-            for (String resource : requiredBundles) {
-                if (!isBundleActive(resource)) {
-                    bundlesToLoad.add(resource);
-                }
-            }
-        }
-        Framework framework = getFramework(this.getClass());
-        List<Bundle> successBundles = loader.installAndStartBundles(framework, bundlesToLoad);
-        for (Bundle bundle : successBundles) {
-            loaded.put(bundle.getLocation(), bundle);
-        }
-    }
-
-    private boolean isBundleActive(String location) {
-        Bundle bundle = loaded.get(location);
-        return (bundle != null) && (bundle.getState() == Bundle.ACTIVE);
-    }
-
-    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/OSGiRegistryTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
-
-import java.util.ArrayList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.launch.Framework;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.bundles.impl.BundleLoader;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(OSGiRegistry.class)
-public class OSGiRegistryTest {
-
-    @Test
-    public void testPreLoadBundles() throws Exception {
-        Framework framework = mock(Framework.class);
-        ArrayList<String> bundleLocations = new ArrayList<>();
-        BundleLoader loader = mock(BundleLoader.class);
-        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
-                withArguments(any()).thenReturn(loader);
-
-        OSGiRegistry.preLoadBundles(framework, bundleLocations, true);
-        verify(loader).installAndStartBundles(framework, bundleLocations);
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundleLoaderTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +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.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import org.junit.Test;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.launch.Framework;
-
-import com.redhat.thermostat.test.Bug;
-
-public class BundleLoaderTest {
-
-    @Test
-    public void verifyBundlesAreInstalledAndStarted() throws BundleException {
-        final String BUNDLE_LOCATION = "bundle-location-1";
-
-        Bundle bundle = mock(Bundle.class);
-        when(bundle.getHeaders()).thenReturn(new Hashtable<>());
-        BundleContext bundleContext = mock(BundleContext.class);
-        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
-        Framework framework = mock(Framework.class);
-        when(framework.getBundleContext()).thenReturn(bundleContext);
-        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
-
-        BundleLoader loader = new BundleLoader();
-        loader.installAndStartBundles(framework, bundleLocations);
-
-        verify(bundle).start(Bundle.START_TRANSIENT);
-    }
-
-    @Bug(id="1227",
-         summary="Make sure launcher does not start fragments",
-         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1227")
-    @Test
-    public void verifyFragmentsAreInstalledButNotStarted() throws BundleException {
-        final String BUNDLE_LOCATION = "bundle-location-1";
-
-        Bundle bundle = mock(Bundle.class);
-        Dictionary<String, String> bundleHeaders = new Hashtable<>();
-        bundleHeaders.put(Constants.FRAGMENT_HOST, "foo-bar");
-        when(bundle.getHeaders()).thenReturn(bundleHeaders);
-
-        BundleContext bundleContext = mock(BundleContext.class);
-        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
-        Framework framework = mock(Framework.class);
-        when(framework.getBundleContext()).thenReturn(bundleContext);
-        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
-
-        BundleLoader loader = new BundleLoader();
-        loader.installAndStartBundles(framework, bundleLocations);
-
-        verify(bundle, times(0)).start(Bundle.START_TRANSIENT);
-
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +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.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.cli.CommandInfo;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({OSGiRegistryImpl.class, FrameworkUtil.class})
-public class OSGiRegistryImplTest {
-
-    private static final String cmdName = "one";
-
-    private static final String jar1Name = "/one.jar";
-    private static final String jar2Name = "/two.jar";
-    private static final String jar3Name = "/three.jar";
-
-    private Bundle b1, b2, b3;
-    private List<String> bundleLocs;
-
-    private BundleLoader loader;
-    private Configuration conf;
-    
-    @Before
-    public void setUp() throws Exception {
-        conf = mock(Configuration.class);
-        when(conf.getThermostatHome()).thenReturn("no_matter");
-        bundleLocs = Arrays.asList(jar1Name, jar2Name, jar3Name);
-        b1 = mock(Bundle.class);
-        when(b1.getLocation()).thenReturn(jar1Name);
-        when(b1.getState()).thenReturn(Bundle.ACTIVE);
-        b2 = mock(Bundle.class);
-        when(b2.getLocation()).thenReturn(jar2Name);
-        when(b2.getState()).thenReturn(Bundle.ACTIVE);
-        b3 = mock(Bundle.class);
-        when(b3.getLocation()).thenReturn(jar3Name);
-        when(b3.getState()).thenReturn(Bundle.ACTIVE);
-        List<Bundle> installed = Arrays.asList(b1, b2, b3);
-
-        loader = mock(BundleLoader.class);
-        when(loader.installAndStartBundles(any(Framework.class), eq(bundleLocs))).
-                thenReturn(installed);
-        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
-                withArguments(any()).thenReturn(loader);
-    }
-
-    @Test
-    public void testLoadBundlesFor() throws Exception {
-        verifyBundlesLoaded(new Bundle[] {}, bundleLocs);
-    }
-
-    @Test
-    public void verifyAlreadyLoadedBundlesNotReloaded() throws Exception {
-        verifyBundlesLoaded(new Bundle[] {b1, b2}, Arrays.asList(jar3Name));
-    }
-
-    private void verifyBundlesLoaded(Bundle[] preloaded, List<String> locationsNeeded) throws Exception {
-        Bundle theBundle = b2;
-        BundleContext theContext = mock(BundleContext.class);
-        when(theContext.getBundles()).thenReturn(preloaded);
-        Framework theFramework = mock(Framework.class);
-        when(theFramework.getBundleContext()).thenReturn(theContext);
-        when(theContext.getBundle(0)).thenReturn(theFramework);
-        when(theBundle.getBundleContext()).thenReturn(theContext);
-        mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
-
-        OSGiRegistryImpl registry = new OSGiRegistryImpl(conf);
-        CommandInfoSource infos = mock(CommandInfoSource.class);
-        CommandInfo info = mock(CommandInfo.class);
-        when (info.getDependencyResourceNames()).thenReturn(bundleLocs);
-        when (infos.getCommandInfo(cmdName)).thenReturn(info);
-        registry.setCommandInfoSource(infos);
-        registry.addBundlesFor(cmdName);
-        verify(loader).installAndStartBundles(any(Framework.class), eq(locationsNeeded));
-    }
-
-    @Test
-    public void verifySetOSGiVerbosityByReflection() throws Exception {
-
-        // All this fluff is just so constructor doesn't NPE.
-        Bundle theBundle = b2;
-        BundleContext theContext = mock(BundleContext.class);
-        when(theContext.getBundles()).thenReturn(new Bundle[]{});
-        Framework theFramework = mock(Framework.class);
-        when(theFramework.getBundleContext()).thenReturn(theContext);
-        when(theContext.getBundle(0)).thenReturn(theFramework);
-        when(theBundle.getBundleContext()).thenReturn(theContext);
-        mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
-
-        Object registry = new OSGiRegistryImpl(conf);
-        Class clazz = registry.getClass();
-        Method m = clazz.getMethod("setPrintOSGiInfo", Boolean.TYPE);
-        m.invoke(registry, true); // If this fails, then API has changed in ways that break FrameworkProvider.
-    }
-
-}
--- a/distribution/pom.xml	Thu Jan 10 21:48:04 2013 +0100
+++ b/distribution/pom.xml	Thu Jan 10 16:16:29 2013 +0100
@@ -322,11 +322,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-launcher</artifactId>
       <version>${project.version}</version>
     </dependency>
--- a/launcher/pom.xml	Thu Jan 10 21:48:04 2013 +0100
+++ b/launcher/pom.xml	Thu Jan 10 16:16:29 2013 +0100
@@ -110,11 +110,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common-test</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/BundleManager.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+
+/**
+ * A Service that provides features to load bundles for given command names.
+ */
+public abstract class BundleManager {
+
+    public abstract void setPrintOSGiInfo(boolean printOSGiInfo);
+
+    public abstract void setCommandInfoSource(CommandInfoSource source);
+
+    public abstract void addBundlesFor(String commandName) throws BundleException, CommandInfoNotFoundException, IOException;
+
+    public static void preLoadBundles(Framework framework, List<String> bundleLocations,
+            boolean printOSGiInfo) throws BundleException {
+        BundleLoader loader = new BundleLoader(printOSGiInfo);
+        loader.installAndStartBundles(framework, bundleLocations);
+    }
+
+    public abstract Configuration getConfiguration();
+
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Thu Jan 10 16:16:29 2013 +0100
@@ -36,70 +36,85 @@
 
 package com.redhat.thermostat.launcher.internal;
 
-import java.util.Map;
-
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.CommandLoadingBundleActivator;
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.Configuration;
 import com.redhat.thermostat.common.cli.CommandContextFactory;
 import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 public class Activator extends CommandLoadingBundleActivator {
+    
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    class RegisterLauncherCustomizer implements ServiceTrackerCustomizer {
 
-    class RegisterLauncherAction implements Action {
-
+        private ServiceRegistration launcherReg;
+        private ServiceRegistration bundleManReg;
+        private ServiceRegistration cmdInfoReg;
         private BundleContext context;
-        private ServiceReference registryReference;
+        private BundleManager bundleService;
 
-        RegisterLauncherAction(BundleContext context) {
+        RegisterLauncherCustomizer(BundleContext context, BundleManager bundleService) {
             this.context = context;
+            this.bundleService = bundleService;
         }
 
         @Override
-        public void dependenciesAvailable(Map<String, Object> services) {
-            
-            registryReference = context.getServiceReference(OSGiRegistry.class);
-            OSGiRegistry bundleService = (OSGiRegistry) context.getService(registryReference);
+        public Object addingService(ServiceReference reference) {
+            // keyring is now ready
+            Keyring keyring = (Keyring)context.getService(reference);
+            // Register Launcher service since FrameworkProvider is waiting for it blockingly.
             CommandInfoSourceImpl commands = new CommandInfoSourceImpl(bundleService.getConfiguration().getThermostatHome());
-            context.registerService(CommandInfoSource.class, commands, null);
+            cmdInfoReg = context.registerService(CommandInfoSource.class, commands, null);
             bundleService.setCommandInfoSource(commands);
             LauncherImpl launcher = new LauncherImpl(context,
                     new CommandContextFactory(context), bundleService);
-            launcherServiceRegistration = context.registerService(Launcher.class.getName(), launcher, null);
+            launcherReg = context.registerService(Launcher.class.getName(), launcher, null);
+            bundleManReg = context.registerService(BundleManager.class, bundleService, null);
+            return keyring;
         }
 
         @Override
-        public void dependenciesUnavailable() {
-            launcherServiceRegistration.unregister();
-            context.ungetService(registryReference);
+        public void modifiedService(ServiceReference reference, Object service) {
+            // nothing
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            // Keyring is gone, remove launcher, et. al. as well
+            launcherReg.unregister();
+            bundleManReg.unregister();
+            cmdInfoReg.unregister();
         }
 
     }
 
     @SuppressWarnings("rawtypes")
-    private ServiceRegistration launcherServiceRegistration;
-    private MultipleServiceTracker tracker;
+    private ServiceTracker serviceTracker;
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public void start(final BundleContext context) throws Exception {
         super.start(context);
-
-        tracker = new MultipleServiceTracker(context, new Class[] {OSGiRegistry.class, Keyring.class}, new RegisterLauncherAction(context));
-        tracker.open();
+        BundleManager bundleService = new BundleManagerImpl(new Configuration());
+        ServiceTrackerCustomizer customizer = new RegisterLauncherCustomizer(context, bundleService);
+        serviceTracker = new ServiceTracker(context, Keyring.class, customizer);
+        // Track for Keyring service.
+        serviceTracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
         super.stop(context);
-        if (tracker != null) {
-            tracker.close();
+        if (serviceTracker != null) {
+            serviceTracker.close();
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleLoader.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,99 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+public class BundleLoader {
+
+    private boolean printOSGiInfo = false;
+
+    BundleLoader() {
+        this(false);
+    }
+
+    public BundleLoader(boolean printOSGiInfo) {
+        setPrintOSGiInfo(printOSGiInfo);
+    }
+
+    public void setPrintOSGiInfo(boolean printOSGiInfo) {
+        this.printOSGiInfo = printOSGiInfo;
+    }
+
+    public List<Bundle> installAndStartBundles(Framework framework,
+            List<String>bundleLocations) throws BundleException {
+        List<Bundle> bundles = new ArrayList<>();
+        BundleContext ctx = framework.getBundleContext();
+        for (String location : bundleLocations) {
+            Bundle bundle = ctx.installBundle(location);
+            if (printOSGiInfo) {
+                System.out.println("BundleLoader: installed bundle: \"" + 
+                        location + "\" as id " + bundle.getBundleId());
+            }
+            bundles.add(bundle);
+        }
+        startBundles(bundles);
+        return bundles;
+    }
+
+    private void startBundles(List<Bundle> bundles) throws BundleException {
+        for (Bundle bundle : bundles) {
+
+            if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
+                if (printOSGiInfo) {
+                    System.out.println("BundleLoader: bundle \"" + bundle.getBundleId() + "\" is a fragment; not starting it");
+                }
+                continue;
+            }
+
+            if (printOSGiInfo) {
+                System.out.println("BundleLoader: starting bundle: \"" + bundle.getBundleId() + "\"");
+            }
+            // We don't want for the framework to set the auto-start bit. Thus, passing
+            // START_TRANSIENT explicitly
+            bundle.start(Bundle.START_TRANSIENT);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleManagerImpl.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,123 @@
+/*
+ * 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 com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.ConfigurationException;
+import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.BundleManager;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+
+public class BundleManagerImpl extends BundleManager {
+
+    private CommandInfoSource commandInfos;
+    private Map<String, Bundle> loaded;
+    private Configuration configuration;
+    private BundleLoader loader;
+
+    BundleManagerImpl(Configuration configuration) throws ConfigurationException, FileNotFoundException, IOException {
+        initLoadedBundles();
+        this.configuration = configuration;
+        loader = new BundleLoader(configuration.getPrintOSGiInfo());
+    }
+
+    private void initLoadedBundles() {
+        loaded = new HashMap<>();
+        Framework framework = getFramework(this.getClass());
+        for (Bundle bundle: framework.getBundleContext().getBundles()) {
+            loaded.put(bundle.getLocation(), bundle);
+        }
+    }
+
+    @Override
+    public void setPrintOSGiInfo(boolean printOSGiInfo) {
+        configuration.setPrintOSGiInfo(printOSGiInfo);
+        loader.setPrintOSGiInfo(printOSGiInfo);
+    }
+
+    @Override
+    public void setCommandInfoSource(CommandInfoSource source) {
+        this.commandInfos = source;
+    }
+
+    @Override
+    public void addBundlesFor(String commandName) throws BundleException, IOException, CommandInfoNotFoundException {
+        if (configuration.getPrintOSGiInfo()) {
+            System.out.println("Loading additional bundles for: " + commandName);
+        }
+        List<String> requiredBundles = commandInfos.getCommandInfo(commandName).getDependencyResourceNames();
+        List<String> bundlesToLoad = new ArrayList<>();
+        if (requiredBundles != null) {
+            for (String resource : requiredBundles) {
+                if (!isBundleActive(resource)) {
+                    bundlesToLoad.add(resource);
+                }
+            }
+        }
+        Framework framework = getFramework(this.getClass());
+        List<Bundle> successBundles = loader.installAndStartBundles(framework, bundlesToLoad);
+        for (Bundle bundle : successBundles) {
+            loaded.put(bundle.getLocation(), bundle);
+        }
+    }
+
+    private boolean isBundleActive(String location) {
+        Bundle bundle = loaded.get(location);
+        return (bundle != null) && (bundle.getState() == Bundle.ACTIVE);
+    }
+
+    private Framework getFramework(Class<?> cls) {
+        return (Framework) FrameworkUtil.getBundle(cls).getBundleContext().getBundle(0);
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Thu Jan 10 16:16:29 2013 +0100
@@ -50,7 +50,6 @@
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.ApplicationService;
@@ -72,6 +71,7 @@
 import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.CommonCommandOptions;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.storage.core.ConnectionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
@@ -89,14 +89,14 @@
     private final Semaphore argsBarrier = new Semaphore(0);
 
     private BundleContext context;
-    private OSGiRegistry registry;
+    private BundleManager registry;
     private final DbServiceFactory dbServiceFactory;
     
-    public LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, OSGiRegistry registry) {
+    public LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, BundleManager registry) {
         this(context, cmdCtxFactory, registry, new LoggingInitializer(), new DbServiceFactory());
     }
 
-    LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, OSGiRegistry registry,
+    LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, BundleManager registry,
             LoggingInitializer loggingInitializer, DbServiceFactory dbServiceFactory) {
         this.context = context;
         this.cmdCtxFactory = cmdCtxFactory;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/BundleManagerTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.launch.Framework;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(BundleManager.class)
+public class BundleManagerTest {
+
+    @Test
+    public void testPreLoadBundles() throws Exception {
+        Framework framework = mock(Framework.class);
+        ArrayList<String> bundleLocations = new ArrayList<>();
+        BundleLoader loader = mock(BundleLoader.class);
+        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
+                withArguments(any()).thenReturn(loader);
+
+        BundleManager.preLoadBundles(framework, bundleLocations, true);
+        verify(loader).installAndStartBundles(framework, bundleLocations);
+    }
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.launcher.internal;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
@@ -46,6 +49,9 @@
 import static org.powermock.api.mockito.PowerMockito.verifyNew;
 import static org.powermock.api.mockito.PowerMockito.whenNew;
 
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
@@ -55,41 +61,55 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 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;
 import com.redhat.thermostat.common.cli.CommandInfo;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
 import com.redhat.thermostat.common.utils.ServiceRegistry;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.launcher.internal.Activator.RegisterLauncherCustomizer;
+import com.redhat.thermostat.test.StubBundleContext;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({Activator.class})
+@PrepareForTest({Activator.class, Activator.RegisterLauncherCustomizer.class, FrameworkUtil.class})
 public class ActivatorTest {
 
     private BundleContext context;
     private MultipleServiceTracker tracker;
     private ServiceReference registryServiceReference, helpCommandReference;
     private ServiceRegistration launcherServiceRegistration, helpCommandRegistration;
-    private OSGiRegistry registryService;
+    private BundleManager registryService;
     private Command helpCommand;
 
     @Before
     public void setUp() throws Exception {
+        Path tempDir = createStubThermostatHome();
+        System.setProperty("THERMOSTAT_HOME", tempDir.toString());
+        
         context = mock(BundleContext.class);
+        setupOsgiRegistryImplMock();
 
         registryServiceReference = mock(ServiceReference.class);
         launcherServiceRegistration = mock(ServiceRegistration.class);
-        registryService = mock(OSGiRegistry.class);
-        when(context.getServiceReference(eq(OSGiRegistry.class))).thenReturn(registryServiceReference);
+        registryService = mock(BundleManager.class);
+        when(context.getServiceReference(eq(BundleManager.class))).thenReturn(registryServiceReference);
         when(context.getService(eq(registryServiceReference))).thenReturn(registryService);
         when(context.registerService(eq(Launcher.class.getName()), any(), (Dictionary) isNull())).
                 thenReturn(launcherServiceRegistration);
@@ -116,32 +136,95 @@
         tracker = mock(MultipleServiceTracker.class);
         whenNew(MultipleServiceTracker.class).
                 withParameterTypes(BundleContext.class, Class[].class, Action.class).
-                withArguments(eq(context), eq(new Class[] {OSGiRegistry.class, Keyring.class}),
+                withArguments(eq(context), eq(new Class[] {BundleManager.class, Keyring.class}),
                         isA(Action.class)).thenReturn(tracker);
     }
 
     @Test
     public void testActivatorLifecycle() throws Exception {
+        ArgumentCaptor<RegisterLauncherCustomizer> customizerCaptor = ArgumentCaptor.forClass(RegisterLauncherCustomizer.class);
+        ServiceTracker mockTracker = mock(ServiceTracker.class);
+        whenNew(ServiceTracker.class).withParameterTypes(BundleContext.class, Class.class, ServiceTrackerCustomizer.class).withArguments(eq(context),
+                any(Keyring.class), customizerCaptor.capture()).thenReturn(mockTracker);
+        
         Activator activator = new Activator();
-
         activator.start(context);
 
         Hashtable<String, Object> props = new Hashtable<>();
         props.put(ServiceRegistry.SERVICE_NAME, "help");
         verify(context).registerService(eq(Command.class.getName()), isA(HelpCommand.class), eq(props));
 
-        ArgumentCaptor<Action> actionCaptor = ArgumentCaptor.forClass(Action.class);
-        verifyNew(MultipleServiceTracker.class).withArguments(eq(context),
-                eq(new Class[] {OSGiRegistry.class, Keyring.class}),
-                actionCaptor.capture());
-        Action action = actionCaptor.getValue();
+        verify(mockTracker).open();
+        
+        RegisterLauncherCustomizer customizer = customizerCaptor.getValue();
+        assertNotNull(customizer);
+        activator.stop(context);
+        verify(mockTracker).close();
+    }
+    
+    @Test
+    public void testServiceTrackerCustomizer() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        ArgumentCaptor<RegisterLauncherCustomizer> customizerCaptor = ArgumentCaptor.forClass(RegisterLauncherCustomizer.class);
+        ServiceTracker mockTracker = mock(ServiceTracker.class);
+        whenNew(ServiceTracker.class).withParameterTypes(BundleContext.class, Class.class, ServiceTrackerCustomizer.class).withArguments(eq(context),
+                any(Keyring.class), customizerCaptor.capture()).thenReturn(mockTracker);
+        
+        Activator activator = new Activator();
+        context.registerService(Keyring.class, mock(Keyring.class), null);
+        activator.start(context);
+        
+        assertTrue(context.isServiceRegistered(Command.class.getName(), HelpCommand.class));
+        
+        RegisterLauncherCustomizer customizer = customizerCaptor.getValue();
+        assertNotNull(customizer);
+        Keyring keyringService = mock(Keyring.class);
+        context.registerService(Keyring.class, keyringService, null);
+        ServiceReference ref = context.getServiceReference(Keyring.class);
+        customizer.addingService(ref);
+        
+        assertTrue(context.isServiceRegistered(CommandInfoSource.class.getName(), mock(CommandInfoSourceImpl.class).getClass()));
+        assertTrue(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class));
+        assertTrue(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class));
 
-        action.dependenciesAvailable(isA(Map.class));
-        verify(context).registerService(eq(Launcher.class.getName()), isA(Launcher.class), (Dictionary) isNull());
+        customizer.removedService(null, null);
+        
+        assertFalse(context.isServiceRegistered(CommandInfoSource.class.getName(), CommandInfoSourceImpl.class));
+        assertFalse(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class));
+        assertFalse(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class));
+    }
+    
+    private Path createStubThermostatHome() throws Exception {
+        Path tempDir = Files.createTempDirectory("test");
+        tempDir.toFile().deleteOnExit();
+        System.setProperty("THERMOSTAT_HOME", tempDir.toString());
+        
+        File tempEtc = new File(tempDir.toFile(), "etc");
+        tempEtc.mkdirs();
+        tempEtc.deleteOnExit();
+        
+        File tempProps = new File(tempEtc, "osgi-export.properties");
+        tempProps.createNewFile();
+        tempProps.deleteOnExit();
 
-        activator.stop(context);
-        // osgi will take care of unregistration on bundle stop
-        // verify(launcherServiceRegistration).unregister();
-        verify(tracker).close();
+        File tempBundleProps = new File(tempEtc, "bundles.properties");
+        tempBundleProps.createNewFile();
+        tempBundleProps.deleteOnExit();
+        
+        File tempLibs = new File(tempDir.toFile(), "libs");
+        tempLibs.mkdirs();
+        tempLibs.deleteOnExit();
+        return tempDir;
+    }
+
+    private void setupOsgiRegistryImplMock() {
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        Bundle mockBundle = mock(Bundle.class);
+        when(FrameworkUtil.getBundle(BundleManagerImpl.class)).thenReturn(mockBundle);
+        when(mockBundle.getBundleContext()).thenReturn(context);
+        Bundle mockFramework = mock(Framework.class);
+        when(context.getBundle(0)).thenReturn(mockFramework);
+        when(mockFramework.getBundleContext()).thenReturn(context);
+        when(context.getBundles()).thenReturn(new Bundle[0]);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleLoaderTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,103 @@
+/*
+ * 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.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+import com.redhat.thermostat.test.Bug;
+
+public class BundleLoaderTest {
+
+    @Test
+    public void verifyBundlesAreInstalledAndStarted() throws BundleException {
+        final String BUNDLE_LOCATION = "bundle-location-1";
+
+        Bundle bundle = mock(Bundle.class);
+        when(bundle.getHeaders()).thenReturn(new Hashtable<String, String>());
+        BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
+        Framework framework = mock(Framework.class);
+        when(framework.getBundleContext()).thenReturn(bundleContext);
+        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
+
+        BundleLoader loader = new BundleLoader();
+        loader.installAndStartBundles(framework, bundleLocations);
+
+        verify(bundle).start(Bundle.START_TRANSIENT);
+    }
+
+    @Bug(id="1227",
+         summary="Make sure launcher does not start fragments",
+         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1227")
+    @Test
+    public void verifyFragmentsAreInstalledButNotStarted() throws BundleException {
+        final String BUNDLE_LOCATION = "bundle-location-1";
+
+        Bundle bundle = mock(Bundle.class);
+        Dictionary<String, String> bundleHeaders = new Hashtable<>();
+        bundleHeaders.put(Constants.FRAGMENT_HOST, "foo-bar");
+        when(bundle.getHeaders()).thenReturn(bundleHeaders);
+
+        BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
+        Framework framework = mock(Framework.class);
+        when(framework.getBundleContext()).thenReturn(bundleContext);
+        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
+
+        BundleLoader loader = new BundleLoader();
+        loader.installAndStartBundles(framework, bundleLocations);
+
+        verify(bundle, times(0)).start(Bundle.START_TRANSIENT);
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleManagerImplTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -0,0 +1,157 @@
+/*
+ * 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.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.cli.CommandInfo;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+import com.redhat.thermostat.launcher.internal.BundleManagerImpl;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({BundleManagerImpl.class, FrameworkUtil.class})
+public class BundleManagerImplTest {
+
+    private static final String cmdName = "one";
+
+    private static final String jar1Name = "/one.jar";
+    private static final String jar2Name = "/two.jar";
+    private static final String jar3Name = "/three.jar";
+
+    private Bundle b1, b2, b3;
+    private List<String> bundleLocs;
+
+    private BundleLoader loader;
+    private Configuration conf;
+    
+    @Before
+    public void setUp() throws Exception {
+        conf = mock(Configuration.class);
+        when(conf.getThermostatHome()).thenReturn("no_matter");
+        bundleLocs = Arrays.asList(jar1Name, jar2Name, jar3Name);
+        b1 = mock(Bundle.class);
+        when(b1.getLocation()).thenReturn(jar1Name);
+        when(b1.getState()).thenReturn(Bundle.ACTIVE);
+        b2 = mock(Bundle.class);
+        when(b2.getLocation()).thenReturn(jar2Name);
+        when(b2.getState()).thenReturn(Bundle.ACTIVE);
+        b3 = mock(Bundle.class);
+        when(b3.getLocation()).thenReturn(jar3Name);
+        when(b3.getState()).thenReturn(Bundle.ACTIVE);
+        List<Bundle> installed = Arrays.asList(b1, b2, b3);
+
+        loader = mock(BundleLoader.class);
+        when(loader.installAndStartBundles(any(Framework.class), eq(bundleLocs))).
+                thenReturn(installed);
+        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
+                withArguments(any()).thenReturn(loader);
+    }
+
+    @Test
+    public void testLoadBundlesFor() throws Exception {
+        verifyBundlesLoaded(new Bundle[] {}, bundleLocs);
+    }
+
+    @Test
+    public void verifyAlreadyLoadedBundlesNotReloaded() throws Exception {
+        verifyBundlesLoaded(new Bundle[] {b1, b2}, Arrays.asList(jar3Name));
+    }
+
+    private void verifyBundlesLoaded(Bundle[] preloaded, List<String> locationsNeeded) throws Exception {
+        Bundle theBundle = b2;
+        BundleContext theContext = mock(BundleContext.class);
+        when(theContext.getBundles()).thenReturn(preloaded);
+        Framework theFramework = mock(Framework.class);
+        when(theFramework.getBundleContext()).thenReturn(theContext);
+        when(theContext.getBundle(0)).thenReturn(theFramework);
+        when(theBundle.getBundleContext()).thenReturn(theContext);
+        mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
+
+        BundleManagerImpl registry = new BundleManagerImpl(conf);
+        CommandInfoSource infos = mock(CommandInfoSource.class);
+        CommandInfo info = mock(CommandInfo.class);
+        when (info.getDependencyResourceNames()).thenReturn(bundleLocs);
+        when (infos.getCommandInfo(cmdName)).thenReturn(info);
+        registry.setCommandInfoSource(infos);
+        registry.addBundlesFor(cmdName);
+        verify(loader).installAndStartBundles(any(Framework.class), eq(locationsNeeded));
+    }
+
+    @Test
+    public void verifySetOSGiVerbosityByReflection() throws Exception {
+
+        // All this fluff is just so constructor doesn't NPE.
+        Bundle theBundle = b2;
+        BundleContext theContext = mock(BundleContext.class);
+        when(theContext.getBundles()).thenReturn(new Bundle[]{});
+        Framework theFramework = mock(Framework.class);
+        when(theFramework.getBundleContext()).thenReturn(theContext);
+        when(theContext.getBundle(0)).thenReturn(theFramework);
+        when(theBundle.getBundleContext()).thenReturn(theContext);
+        mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
+
+        Object registry = new BundleManagerImpl(conf);
+        Class clazz = registry.getClass();
+        Method m = clazz.getMethod("setPrintOSGiInfo", Boolean.TYPE);
+        m.invoke(registry, true); // If this fails, then API has changed in ways that break FrameworkProvider.
+    }
+
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -72,7 +72,6 @@
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.ApplicationInfo;
@@ -93,6 +92,7 @@
 import com.redhat.thermostat.common.tools.ApplicationState;
 import com.redhat.thermostat.common.tools.BasicCommand;
 import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.launcher.internal.LauncherImpl.LoggingInitializer;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.test.StubBundleContext;
@@ -144,7 +144,7 @@
     private StubBundleContext bundleContext;
     private Bundle sysBundle;
     private TestTimerFactory timerFactory;
-    private OSGiRegistry registry;
+    private BundleManager registry;
     private LoggingInitializer loggingInitializer;
     private DbServiceFactory dbServiceFactory;
     private CommandInfoSource infos;
@@ -211,7 +211,7 @@
 
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3, basicCmd));
 
-        registry = mock(OSGiRegistry.class);
+        registry = mock(BundleManager.class);
 
         infos = mock(CommandInfoSource.class);
         when(infos.getCommandInfo(name1)).thenReturn(info1);
--- a/main/pom.xml	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/pom.xml	Thu Jan 10 16:16:29 2013 +0100
@@ -84,11 +84,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-launcher</artifactId>
       <version>${project.version}</version>
     </dependency>
--- a/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Thu Jan 10 16:16:29 2013 +0100
@@ -62,6 +62,7 @@
         this.context = context;
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     private void launch()
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
             FileNotFoundException, IOException, BundleException, InterruptedException {
--- a/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Thu Jan 10 16:16:29 2013 +0100
@@ -59,10 +59,9 @@
 import org.osgi.framework.launch.FrameworkFactory;
 import org.osgi.util.tracker.ServiceTracker;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.ConfigurationException;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
 
 public class FrameworkProvider {
 
@@ -217,11 +216,11 @@
                 locations.add(location);
             }
         }
-        OSGiRegistry.preLoadBundles(framework, locations, printOSGiInfo);
+        BundleManager.preLoadBundles(framework, locations, printOSGiInfo);
     }
 
     private void setLoaderVerbosity(Framework framework) throws InterruptedException {
-        Object loader = getService(framework, OSGiRegistry.class.getName());
+        Object loader = getService(framework, BundleManager.class.getName());
         callVoidReflectedMethod(loader, "setPrintOSGiInfo", printOSGiInfo, Boolean.TYPE);
     }
 
@@ -232,6 +231,7 @@
 
     private Object getService(Framework framework, String name) throws InterruptedException {
         Object service = null;
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         ServiceTracker tracker = new ServiceTracker(framework.getBundleContext(), name, null);
         tracker.open();
         service = tracker.waitForService(0);
--- a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Thu Jan 10 16:16:29 2013 +0100
@@ -1,7 +1,6 @@
 bundles=thermostat-keyring-${project.version}.jar, \
         thermostat-storage-core-${project.version}.jar, \
         thermostat-common-core-${project.version}.jar, \
-        thermostat-bundles-${project.version}.jar, \
         thermostat-launcher-${project.version}.jar, \
         thermostat-main-${project.version}.jar, \
         jline2.jar, \
--- a/main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java	Thu Jan 10 16:16:29 2013 +0100
@@ -36,27 +36,36 @@
 
 package com.redhat.thermostat.main;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Hashtable;
 
 import org.junit.Before;
+import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.bundles.impl.OSGiRegistryImpl;
-import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.main.impl.FrameworkProvider;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(value = Thermostat.class)
+@PrepareForTest({FrameworkProvider.class})
 public class ThermostatTest {
 
     private Path tempDir;
@@ -65,12 +74,9 @@
 
     private BundleContext mockContext;
 
+    @SuppressWarnings("rawtypes")
     @Before
     public void setUp() throws Exception {
-
-        final OSGiRegistryImpl osgiRegistry = mock(OSGiRegistryImpl.class);
-        PowerMockito.whenNew(OSGiRegistryImpl.class).withArguments(any(Configuration.class)).thenReturn(osgiRegistry);
-
         tempDir = Files.createTempDirectory("test");
         tempDir.toFile().deleteOnExit();
         System.setProperty("THERMOSTAT_HOME", tempDir.toString());
@@ -93,19 +99,40 @@
         
         mockContext = mock(BundleContext.class);
 
-        mockFramework = mock(Framework.class);
-        when(mockFramework.getBundleContext()).thenReturn(mockContext);
-
-        TestFrameworkFactory.setFramework(mockFramework);
+        Framework framework = mock(Framework.class);
+        TestFrameworkFactory.setFramework(framework);
+        when(framework.getBundleContext()).thenReturn(mockContext);
+        Bundle mockBundle = mock(Bundle.class);
+        when(mockContext.installBundle(any(String.class))).thenReturn(mockBundle);
+        when(mockBundle.getHeaders()).thenReturn(new Hashtable<String, String>());
+        ServiceTracker registryTracker = mock(ServiceTracker.class);
+        PowerMockito
+                .whenNew(ServiceTracker.class)
+                .withParameterTypes(BundleContext.class, String.class,
+                        ServiceTrackerCustomizer.class)
+                .withArguments(any(BundleContext.class),
+                        eq(BundleManager.class.getName()), any(ServiceTrackerCustomizer.class))
+                .thenReturn(registryTracker);
+        when(registryTracker.waitForService(0)).thenReturn(mock(BundleManager.class));
+        ServiceTracker launcherTracker = mock(ServiceTracker.class);
+        Launcher launcher = mock(Launcher.class);
+        PowerMockito
+                .whenNew(ServiceTracker.class)
+                .withParameterTypes(BundleContext.class, String.class,
+                        ServiceTrackerCustomizer.class)
+                .withArguments(any(BundleContext.class),
+                        eq(Launcher.class.getName()),
+                        any(ServiceTrackerCustomizer.class))
+                .thenReturn(launcherTracker);
+        when(launcherTracker.waitForService(0))
+                .thenReturn(launcher);
     }
 
-    // TODO These now seem to belong in OSGiRegistryTest
-
-    /*
     @Test
     public void testOSGIDirExists() throws Exception {
-        Path osgiDir = tempDir.resolve("osgi");
+        Path osgiDir = tempDir.resolve("osgi-cache");
         osgiDir.toFile().mkdirs();
+        osgiDir.toFile().deleteOnExit();
         assertTrue(osgiDir.toFile().exists());
         try {
             Thermostat.main(new String[0]);
@@ -113,20 +140,18 @@
             e.printStackTrace();
         }
         assertTrue(osgiDir.toFile().exists());
-    }*/
-
-    /*@Test
-    public void testFrameworkConfig() throws Exception {
-        Thermostat.main(new String[0]);
-        Map<String,String> config = TestFrameworkFactory.getConfig();
-        Path osgiDir = tempDir.resolve("osgi");
-        assertEquals(osgiDir.toString(), config.get(Constants.FRAMEWORK_STORAGE));
     }
 
     @Test
     public void testFrameworkInitAndStart() throws Exception {
+        Path osgiDir = tempDir.resolve("osgi-cache");
+        osgiDir.toFile().mkdirs();
+        osgiDir.toFile().deleteOnExit();
+        mockFramework = mock(Framework.class);
+        when(mockFramework.getBundleContext()).thenReturn(mockContext);
+        TestFrameworkFactory.setFramework(mockFramework);
         Thermostat.main(new String[0]);
         verify(mockFramework).init();
         verify(mockFramework).start();
-    }*/
+    }
 }
--- a/main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory	Thu Jan 10 21:48:04 2013 +0100
+++ b/main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory	Thu Jan 10 16:16:29 2013 +0100
@@ -34,4 +34,4 @@
 # to do so, delete this exception statement from your version.
 #
 
-com.redhat.thermostat.launcher.TestFrameworkFactory
\ No newline at end of file
+com.redhat.thermostat.main.TestFrameworkFactory
\ No newline at end of file
--- a/pom.xml	Thu Jan 10 21:48:04 2013 +0100
+++ b/pom.xml	Thu Jan 10 16:16:29 2013 +0100
@@ -120,7 +120,6 @@
     <module>distribution</module>
     <module>main</module>
     <module>launcher</module>
-    <module>bundles</module>
     <module>common</module>
     <module>agent</module>
     <module>client</module>