changeset 334:feac4ec61dae

Shut down the storage cleanly Remove exceptions thrown on exit by converting shutdown-hooks into command-disable hooks and disable commands when the bundle stops. Reviewed-by: rkennke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-May/001525.html
author Omair Majid <omajid@redhat.com>
date Tue, 29 May 2012 11:30:11 -0400
parents f3f4efd17456
children b84371fa2745
files agent/src/main/java/com/redhat/thermostat/agent/Activator.java agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java client/core/src/main/java/com/redhat/thermostat/client/GUIClientCommand.java client/core/src/main/java/com/redhat/thermostat/client/osgi/ThermostatActivator.java common/src/main/java/com/redhat/thermostat/common/Activator.java common/src/main/java/com/redhat/thermostat/common/cli/Command.java common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistry.java common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java common/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java common/src/main/java/com/redhat/thermostat/test/TestCommandRegistry.java common/src/test/java/com/redhat/thermostat/common/ActivatorTest.java common/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java common/src/test/java/com/redhat/thermostat/common/cli/LauncherTest.java common/src/test/java/com/redhat/thermostat/common/cli/TestCommand.java common/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java launcher/src/main/java/com/redhat/thermostat/launcher/Thermostat.java tools/src/main/java/com/redhat/thermostat/tools/Activator.java tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java tools/src/main/java/com/redhat/thermostat/tools/cli/ShellCommand.java tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command
diffstat 23 files changed, 254 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/agent/src/main/java/com/redhat/thermostat/agent/Activator.java	Tue May 29 12:19:43 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/Activator.java	Tue May 29 11:30:11 2012 -0400
@@ -37,30 +37,26 @@
 package com.redhat.thermostat.agent;
 
 import java.util.Arrays;
-import java.util.Collection;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
 public class Activator implements BundleActivator {
 
-    private Collection<ServiceRegistration> agentCmd;
+    private CommandRegistry reg;
 
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
-        agentCmd = reg.registerCommands(Arrays.asList(new AgentApplication()));
+        reg = new CommandRegistryImpl(context);
+        reg.registerCommands(Arrays.asList(new AgentApplication()));
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        for (ServiceRegistration reg : agentCmd) {
-            reg.unregister();
-        }
+        reg.unregisterCommands();
     }
 
 }
--- a/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Tue May 29 12:19:43 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Tue May 29 11:30:11 2012 -0400
@@ -178,6 +178,11 @@
     }
 
     @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/client/core/src/main/java/com/redhat/thermostat/client/GUIClientCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/GUIClientCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -69,6 +69,11 @@
     }
 
     @Override
+    public void disable() {
+        // TODO clientMain.shutdown();
+    }
+
+    @Override
     public String getName() {
         return "gui";
     }
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/ThermostatActivator.java	Tue May 29 12:19:43 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/ThermostatActivator.java	Tue May 29 11:30:11 2012 -0400
@@ -45,25 +45,28 @@
 import com.redhat.thermostat.client.Main;
 import com.redhat.thermostat.client.UiFacadeFactory;
 import com.redhat.thermostat.client.UiFacadeFactoryImpl;
+import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
 public class ThermostatActivator implements BundleActivator {
 
     private VmInformationServiceTracker vmInfoServiceTracker;
     private VMContextActionServiceTracker contextActionTracker;
-    
+
+    private CommandRegistry cmdReg;
+
     @Override
     public void start(final BundleContext context) throws Exception {
         UiFacadeFactory uiFacadeFactory = new UiFacadeFactoryImpl();
-        
+
         vmInfoServiceTracker = new VmInformationServiceTracker(context, uiFacadeFactory);
         vmInfoServiceTracker.open();
-        
+
         contextActionTracker =
                 new VMContextActionServiceTracker(context, uiFacadeFactory);
         contextActionTracker.open();
 
-        CommandRegistryImpl cmdReg = new CommandRegistryImpl(context);
+        cmdReg = new CommandRegistryImpl(context);
         Main main = new Main(uiFacadeFactory, new String[0]);
         cmdReg.registerCommands(Arrays.asList(new GUIClientCommand(main)));
     }
@@ -72,5 +75,6 @@
     public void stop(BundleContext context) throws Exception {
         vmInfoServiceTracker.close(); //context.removeServiceListener(vmInfoServiceTracker);
         contextActionTracker.close();
+        cmdReg.unregisterCommands();
     }
 }
--- a/common/src/main/java/com/redhat/thermostat/common/Activator.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/Activator.java	Tue May 29 11:30:11 2012 -0400
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.common;
 
-import java.util.Collection;
 import java.util.ServiceLoader;
 
 import org.osgi.framework.BundleActivator;
@@ -52,14 +51,14 @@
 
 public class Activator implements BundleActivator {
 
-    private Collection<ServiceRegistration> cmdRegs;
     private ServiceRegistration launcherReg;
+    private CommandRegistry reg;
 
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
+        reg = new CommandRegistryImpl(context);
         ServiceLoader<Command> cmds = ServiceLoader.load(Command.class, getClass().getClassLoader());
-        cmdRegs = reg.registerCommands(cmds);
+        reg.registerCommands(cmds);
 
         CommandContextFactory cmdCtxFactory = new CommandContextFactory(context);
         Launcher launcher = new LauncherImpl(cmdCtxFactory);
@@ -69,9 +68,7 @@
     @Override
     public void stop(BundleContext context) throws Exception {
         launcherReg.unregister();
-        for (ServiceRegistration cmdReg : cmdRegs) {
-            cmdReg.unregister();
-        }
+        reg.unregisterCommands();
     }
 
 }
--- a/common/src/main/java/com/redhat/thermostat/common/cli/Command.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/Command.java	Tue May 29 11:30:11 2012 -0400
@@ -38,19 +38,50 @@
 
 import java.util.Collection;
 
-
+/**
+ * Represents a command on the command line.
+ * <p>
+ * To register a custom command, have a class implement this interface and
+ * register it as an OSGi service with the {@link #NAME} set to the value of
+ * {@link #getName()}.
+ */
 public interface Command {
 
-    String NAME = "COMMAND_NAME";
+    static final String NAME = "COMMAND_NAME";
 
+    /**
+     * Execute the command
+     */
     void run(CommandContext ctx) throws CommandException;
 
+    /**
+     * Called when the command is being removed from the system. The command
+     * should cancel any long-term action it has taken, such as any background
+     * tasks or threads it has spawned.
+     */
+    void disable();
+
+    /**
+     * Returns a name for this command. This will be used by the user to select
+     * this command.
+     */
     String getName();
 
+    /**
+     * A short description for the command indicating what it does.
+     */
     String getDescription();
 
+    /**
+     * How the user should invoke this command
+     */
     String getUsage();
 
+    /**
+     * Returns a collection of arguments that the command is prepared to handle.
+     * If the user provides unknown or malformed arguments, this command will
+     * not be invoked.
+     */
     Collection<ArgumentSpec> getAcceptedArguments();
 
     boolean isStorageRequired();
--- a/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistry.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistry.java	Tue May 29 11:30:11 2012 -0400
@@ -44,6 +44,8 @@
 
     public abstract Collection<ServiceRegistration> registerCommands(Iterable<? extends Command> cmds);
 
+    public abstract void unregisterCommands();
+
     public abstract Command getCommand(String name);
 
     public abstract Collection<Command> getRegisteredCommands();
--- a/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Tue May 29 11:30:11 2012 -0400
@@ -39,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.logging.Logger;
 
@@ -53,6 +54,8 @@
 
     private BundleContext context;
 
+    private List<ServiceRegistration> ourRegistrations = new ArrayList<ServiceRegistration>();
+
     public CommandRegistryImpl(BundleContext ctx) {
         context = ctx;
     }
@@ -60,12 +63,27 @@
     protected ServiceRegistration registerCommand(Command cmd) {
         Hashtable<String, String> props = new Hashtable<>();
         props.put(Command.NAME, cmd.getName());
-        return context.registerService(Command.class.getName(), cmd, props);
+        ServiceRegistration registration = context.registerService(Command.class.getName(), cmd, props);
+        ourRegistrations.add(registration);
+        return registration;
+    }
+
+    @Override
+    public void unregisterCommands() {
+        Iterator<ServiceRegistration> iter = ourRegistrations.iterator();
+        while (iter.hasNext()) {
+            ServiceRegistration registration = iter.next();
+            Object serviceObject =  context.getService(registration.getReference());
+            Command cmd = (Command) serviceObject;
+            cmd.disable();
+            registration.unregister();
+            iter.remove();
+        }
     }
 
     @Override
     public Command getCommand(String name) {
-        ServiceReference[] refs = getCommandServiceRefs("(&(objectclass=*)(COMMAND_NAME=" + name + "))");
+        ServiceReference[] refs = getCommandServiceRefs("(&(objectclass=*)(" + Command.NAME + "=" + name + "))");
         if (refs == null || refs.length == 0) {
             return null;
         } else if (refs.length > 1) {
--- a/common/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -92,6 +92,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/common/src/main/java/com/redhat/thermostat/test/TestCommandRegistry.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/test/TestCommandRegistry.java	Tue May 29 11:30:11 2012 -0400
@@ -66,7 +66,7 @@
         public void unregister() {
             // Nothing to do for now.
         }
-        
+
     }
 
     private Map<String, Command> commands = new HashMap<>();
@@ -83,4 +83,9 @@
         commands.put(cmd.getName(), cmd);
         return new TestServiceRegistration();
     }
+
+    @Override
+    public void unregisterCommands() {
+        commands.clear();
+    }
 }
--- a/common/src/test/java/com/redhat/thermostat/common/ActivatorTest.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/ActivatorTest.java	Tue May 29 11:30:11 2012 -0400
@@ -47,12 +47,16 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.cli.Command;
@@ -63,17 +67,31 @@
 
     @Test
     public void testRegisterServices() throws Exception {
-        final Collection<ServiceRegistration> regs = new ArrayList<>();
+        final Map<ServiceRegistration, Object> regs = new HashMap<>();
         BundleContext bCtx = mock(BundleContext.class);
         when(bCtx.registerService(anyString(), any(), any(Dictionary.class))).then(new Answer<ServiceRegistration>() {
 
             @Override
             public ServiceRegistration answer(InvocationOnMock invocation) throws Throwable {
                 ServiceRegistration reg = mock(ServiceRegistration.class);
-                regs.add(reg);
+                when(reg.getReference()).thenReturn(mock(ServiceReference.class));
+                regs.put(reg, invocation.getArguments()[1]);
                 return reg;
             }
         });
+        when(bCtx.getService(isA(ServiceReference.class))).then(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                ServiceReference ref = (ServiceReference) invocation.getArguments()[0];
+                for (Entry<ServiceRegistration,Object> registration: regs.entrySet()) {
+                    if (registration.getKey().getReference().equals(ref)) {
+                        return registration.getValue();
+                    }
+                }
+                return null;
+            }
+        });
+
         Activator activator = new Activator();
 
         activator.start(bCtx);
@@ -86,7 +104,7 @@
 
         activator.stop(bCtx);
 
-        for (ServiceRegistration reg : regs) {
+        for (ServiceRegistration reg : regs.keySet()) {
             verify(reg).unregister();
         }
     }
--- a/common/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Tue May 29 11:30:11 2012 -0400
@@ -40,6 +40,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -55,6 +56,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 
 public class CommandRegistryImplTest {
 
@@ -88,6 +90,51 @@
         props2.put(Command.NAME, "test2");
         verify(bundleContext).registerService(Command.class.getName(), cmd1, props1);
         verify(bundleContext).registerService(Command.class.getName(), cmd2, props2);
+
+        verifyNoMoreInteractions(bundleContext);
+
+    }
+
+    @Test
+    public void testUnregisterCommand() {
+        Command cmd1 = mock(Command.class);
+        when(cmd1.getName()).thenReturn("test1");
+        Command cmd2 = mock(Command.class);
+        when(cmd2.getName()).thenReturn("test2");
+
+        ServiceReference cmd1Reference = mock(ServiceReference.class);
+        ServiceReference cmd2Reference = mock(ServiceReference.class);
+
+        ServiceRegistration cmd1Reg = mock(ServiceRegistration.class);
+        when(cmd1Reg.getReference()).thenReturn(cmd1Reference);
+        ServiceRegistration cmd2Reg = mock(ServiceRegistration.class);
+        when(cmd2Reg.getReference()).thenReturn(cmd2Reference);
+
+        Hashtable<String,String> props1 = new Hashtable<>();
+        props1.put(Command.NAME, cmd1.getName());
+        Hashtable<String,String> props2 = new Hashtable<>();
+        props2.put(Command.NAME, cmd2.getName());
+
+        when(bundleContext.registerService(Command.class.getName(), cmd1, props1)).thenReturn(cmd1Reg);
+        when(bundleContext.registerService(Command.class.getName(), cmd2, props2)).thenReturn(cmd2Reg);
+
+        commandRegistry.registerCommands(Arrays.asList(cmd1, cmd2));
+
+        verify(bundleContext).registerService(Command.class.getName(), cmd1, props1);
+        verify(bundleContext).registerService(Command.class.getName(), cmd2, props2);
+
+        when(bundleContext.getService(eq(cmd1Reference))).thenReturn(cmd1);
+        when(bundleContext.getService(eq(cmd2Reference))).thenReturn(cmd2);
+
+        commandRegistry.unregisterCommands();
+
+        verify(bundleContext).getService(cmd1Reference);
+        verify(cmd1).disable();
+        verify(cmd1Reg).unregister();
+        verify(bundleContext).getService(cmd2Reference);
+        verify(cmd2).disable();
+        verify(cmd2Reg).unregister();
+
         verifyNoMoreInteractions(bundleContext);
     }
 
--- a/common/src/test/java/com/redhat/thermostat/common/cli/LauncherTest.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/LauncherTest.java	Tue May 29 11:30:11 2012 -0400
@@ -64,6 +64,9 @@
             ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
         }
 
+        @Override
+        public void stop() { /* N0-OP */ }
+
     }
 
     private static class TestCmd2 implements TestCommand.Handle {
@@ -72,6 +75,11 @@
             Arguments args = ctx.getArguments();
             ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
         }
+
+        @Override
+        public void stop() {
+            /* NO-OP */
+        }
     }
 
     private TestCommandContextFactory  ctxFactory;
@@ -208,6 +216,9 @@
             public void run(CommandContext ctx) throws CommandException {
                 throw new CommandException("test error");
             }
+
+            @Override
+            public void stop() { /* NO-OP */ }
         });
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
 
--- a/common/src/test/java/com/redhat/thermostat/common/cli/TestCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/TestCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -54,6 +54,7 @@
 
     static interface Handle {
         public void run(CommandContext ctx) throws CommandException;
+        public void stop();
     }
 
     TestCommand(String name) {
@@ -73,6 +74,13 @@
     }
 
     @Override
+    public void disable() {
+        if (handle != null) {
+            handle.stop();
+        }
+    }
+
+    @Override
     public String getName() {
         return name;
     }
--- a/common/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Tue May 29 11:30:11 2012 -0400
@@ -64,6 +64,11 @@
             }
 
             @Override
+            public void disable() {
+                // Move along
+            }
+
+            @Override
             public String getName() {
                 return null;
             }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/Thermostat.java	Tue May 29 12:19:43 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/Thermostat.java	Tue May 29 11:30:11 2012 -0400
@@ -40,6 +40,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -54,13 +55,15 @@
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 
-
 public class Thermostat {
 
     /**
-     * 
+     * the name of the launcher class
      */
     private static final String LAUNCHER_CLASSNAME = "com.redhat.thermostat.common.cli.Launcher";
+
+    private static final String DEBUG_PREFIX = "OSGi Launcher: ";
+
     private File thermostatBundleHome;
     private boolean printOSGiDebugInfo = false;
 
@@ -80,7 +83,7 @@
         BundleContext bundleContext = framework.getBundleContext();
         for (String location : bundleLocations) {
             if (printOSGiDebugInfo) {
-                System.out.print("installing bundle: \"" + location + "\"");
+                System.out.print(DEBUG_PREFIX + "installing bundle: \"" + location + "\"");
             }
             Bundle bundle = bundleContext.installBundle(location);
             if (printOSGiDebugInfo) {
@@ -95,7 +98,7 @@
     private void startBundles(List<Bundle> bundles) throws BundleException {
         for (Bundle bundle : bundles) {
             if (printOSGiDebugInfo) {
-                System.out.println("starting bundle: \"" + bundle.getBundleId() + "\"");
+                System.out.println(DEBUG_PREFIX + "starting bundle: \"" + bundle.getBundleId() + "\"");
             }
             bundle.start();
         }
@@ -117,11 +120,27 @@
         if (factories.hasNext()) {
 
             // we just want the first found
-            Framework framework = factories.next().newFramework(bundleConfigurations);
+            final Framework framework = factories.next().newFramework(bundleConfigurations);
             framework.init();
             List<String> bundles = OSGiRegistry.getSystemBundles();
             List<Bundle> bundleList = installBundles(framework, bundles);
             framework.start();
+
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        framework.stop();
+                        framework.waitForStop(0);
+                        if (printOSGiDebugInfo) {
+                            System.out.println(DEBUG_PREFIX + "OSGi framework has shut down");
+                        }
+                    } catch (Exception e) {
+                        System.err.println("Error stopping framework:" + e);
+                    }
+                }
+            });
+
             startBundles(bundleList);
 
             launch(args, framework);
@@ -141,6 +160,9 @@
         if (launcherRef != null) {
             Object launcherImpl = ctx.getService(launcherRef);
             Method m = launcherImpl.getClass().getMethod("run", String[].class);
+            if (printOSGiDebugInfo) {
+                System.out.println(DEBUG_PREFIX + "invoking " + launcherImpl.getClass().getName() + "." + m.getName());
+            }
             m.invoke(launcherImpl, (Object) args);
         } else {
             System.err.println("Severe: Could not locate launcher");
@@ -158,10 +180,17 @@
     public static void main(String[] args) throws Exception {
 
         Thermostat t = new Thermostat();
-        if (args.length >= 1 && args[0].equals("--print-osgi-info")) {
-            t.setPrintOSGiDebugInfo(true);
+        List<String> toProcess = new ArrayList<>(Arrays.asList(args));
+        Iterator<String> iter = toProcess.iterator();
+        while (iter.hasNext()) {
+            String arg = iter.next();
+            if (("--print-osgi-info").equals(arg)) {
+                t.setPrintOSGiDebugInfo(true);
+                iter.remove();
+            }
         }
-        t.start(args);
+
+        t.start(toProcess.toArray(new String[0]));
     }
 
 
--- a/tools/src/main/java/com/redhat/thermostat/tools/Activator.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/Activator.java	Tue May 29 11:30:11 2012 -0400
@@ -47,17 +47,18 @@
 
 public class Activator implements BundleActivator {
 
+    private CommandRegistry reg;
+
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
+        reg = new CommandRegistryImpl(context);
         ServiceLoader<Command> cmds = ServiceLoader.load(Command.class, getClass().getClassLoader());
         reg.registerCommands(cmds);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        // TODO Auto-generated method stub
-
+        reg.unregisterCommands();
     }
 
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Tue May 29 11:30:11 2012 -0400
@@ -38,6 +38,8 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -80,6 +82,8 @@
 
     private CommandContext context;
 
+    private List<Runnable> tasksOnStop = new CopyOnWriteArrayList<>();
+
     public ThermostatService() {
         database = new DBService();
         agent = new AgentApplication();
@@ -125,8 +129,8 @@
             // we started the database ourselves
             case START:
                 
-                // set a shutdown hook if the db was started by us
-                Runtime.getRuntime().addShutdownHook(new Thread() {
+                // set a bundle-stop hook if the db was started by us
+                tasksOnStop.add(new Runnable() {
                     @Override
                     public void run() {
                         String[] args = new String[] { "storage", "--stop" };
@@ -147,6 +151,7 @@
                     notifier.fireAction(ApplicationState.FAIL);
                 }
                 break;
+
             case FAIL:
                 System.err.println("error starting db");
                 notifier.fireAction(ApplicationState.FAIL);
@@ -163,6 +168,13 @@
     }
 
     @Override
+    public void disable() {
+        for (Runnable task: tasksOnStop) {
+            task.run();
+        }
+    }
+
+    @Override
     public String getName() {
         return NAME;
     }
@@ -183,4 +195,6 @@
         ArgumentSpec stop = new SimpleArgumentSpec("stop", "stop the database and agent");
         return Arrays.asList(start, stop);
     }
+
+
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -77,6 +77,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/ShellCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ShellCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -121,6 +121,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Tue May 29 11:30:11 2012 -0400
@@ -88,6 +88,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Tue May 29 11:30:11 2012 -0400
@@ -183,7 +183,12 @@
             throw new InvalidConfigurationException("database directories do not exist...");
         }
     }
-    
+
+    @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
     @Override
     public DBStartupConfiguration getConfiguration() {
         return configuration;
--- a/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Tue May 29 11:30:11 2012 -0400
@@ -1,6 +1,5 @@
 com.redhat.thermostat.tools.ThermostatService
 com.redhat.thermostat.tools.db.DBService
-#com.redhat.thermostat.agent.AgentApplication
 com.redhat.thermostat.tools.cli.ListVMsCommand
 com.redhat.thermostat.tools.cli.ShellCommand
 com.redhat.thermostat.tools.cli.VMInfoCommand