changeset 913:bf46bf74d05c

Merge
author Roman Kennke <rkennke@redhat.com>
date Tue, 15 Jan 2013 23:04:57 +0100
parents 6815308bb362 (current diff) 03948c38134a (diff)
children 8af946d24b9d
files agent/core/src/main/java/com/redhat/thermostat/agent/JvmStatusListener.java agent/core/src/main/java/com/redhat/thermostat/agent/JvmStatusNotifier.java agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java 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 common/core/src/main/java/com/redhat/thermostat/test/StubFilter.java distribution/pom.xml pom.xml vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcHostListener.java vm-gc/agent/src/test/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcHostListenerTest.java
diffstat 133 files changed, 4041 insertions(+), 1802 deletions(-) [+]
line wrap: on
line diff
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/RequestReceiver.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/RequestReceiver.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.agent.command;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.Response;
 
@@ -48,6 +49,7 @@
  *
  * @see ReceiverRegistry#registerReceiver(RequestReceiver)
  */
+@ExtensionPoint
 public interface RequestReceiver {
 
     public Response receive(Request request);
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/JvmStatusListener.java	Tue Jan 15 23:00:52 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +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.agent;
-
-/**
- * A listener that is notified when a JVM starts or is stopped.
- */
-public interface JvmStatusListener {
-
-    public void jvmStarted(int pid);
-
-    public void jvmStopped(int pid);
-}
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/JvmStatusNotifier.java	Tue Jan 15 23:00:52 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +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.agent;
-
-public interface JvmStatusNotifier {
-
-    /**
-     * Request to be informed when JVM processes are started or stopped.
-     * 
-     * @param listener the receiver of future {@link JvmStatusListener.jvmStarted()}
-     * and {@link JvmStatusListener.jvmStopped()} calls
-     */
-    public void addJvmStatusListener(JvmStatusListener listener);
-
-    /**
-     * Request to no longer be informed when JVM processes are started or stopped.
-     * @param listener the {@link JvmStatusListener} to be unregistered.
-     */
-    public void removeJvmStatusListener(JvmStatusListener listener);
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/VmStatusListener.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.agent;
+
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
+/**
+ * A listener that is notified when a JVM starts or is stopped.
+ * <p>
+ * Register an instance of this class as an OSGi service, and it will be
+ * notified of all VM {@link Status} events.
+ */
+@ExtensionPoint
+public interface VmStatusListener {
+
+    enum Status {
+
+        /** A VM has just started now */
+        VM_STARTED,
+
+        /**
+         * A delayed version of {@link #VM_STARTED}. A VM was started at some
+         * point in the past. The listener was not informed about it then
+         * (probably because the listener was not registered at the time), but
+         * is being informed about it now.
+         */
+        VM_ACTIVE,
+
+        /** A VM was stopped just now */
+        VM_STOPPED,
+    }
+
+    // TODO what other information other than pid may be useful for plugins?
+
+    public void vmStatusChanged(Status newStatus, int pid);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/VmStatusListenerRegistrar.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.agent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Registers a {@link VmStatusListener} as an OSGi Service.
+ * <p>
+ * This is just a convenience wrapper over
+ * {@link BundleContext#registerService(String, Object, java.util.Dictionary)}.
+ * It does absolutely nothing special.
+ *
+ * @see VmStatusListener
+ */
+public class VmStatusListenerRegistrar {
+
+    private final BundleContext context;
+    private final Map<VmStatusListener, ServiceRegistration> registrations = new HashMap<>();
+
+    public VmStatusListenerRegistrar(BundleContext context) {
+        this.context = context;
+    }
+
+    public void register(VmStatusListener listener) {
+        registrations.put(listener, context.registerService(VmStatusListener.class.getName(), listener, null));
+    }
+
+    public void unregister(VmStatusListener listener) {
+        ServiceRegistration registration = registrations.remove(listener);
+        if (registration == null) {
+            throw new IllegalArgumentException("no listener found");
+        }
+
+        registration.unregister();
+    }
+}
--- a/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Tue Jan 15 23:04:57 2013 +0100
@@ -40,6 +40,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.common.LaunchException;
 import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.dao.DAOFactory;
@@ -48,7 +49,11 @@
 /**
  * Represents a plugin that runs on the agent and performs monitoring of host
  * and applications.
+ * <p>
+ * To register a new backend, register an instance of the class with the OSGi
+ * service registry.
  */
+@ExtensionPoint
 public abstract class Backend implements Ordered {
 
     private boolean initialConfigurationComplete = false;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/VmStatusListenerRegistrarTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.agent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class VmStatusListenerRegistrarTest {
+
+    @Test
+    public void testRegister() {
+        StubBundleContext context = new StubBundleContext();
+        VmStatusListener listener = mock(VmStatusListener.class);
+
+        VmStatusListenerRegistrar registerer = new VmStatusListenerRegistrar(context);
+        registerer.register(listener);
+
+        assertTrue(context.isServiceRegistered(VmStatusListener.class.getName(), listener.getClass()));
+        assertEquals(1, context.getAllServices().size());
+    }
+
+    @Test
+    public void testUnregister() {
+        StubBundleContext context = new StubBundleContext();
+        VmStatusListener listener = mock(VmStatusListener.class);
+
+        VmStatusListenerRegistrar registerer = new VmStatusListenerRegistrar(context);
+        registerer.register(listener);
+        registerer.unregister(listener);
+
+        assertFalse(context.isServiceRegistered(VmStatusListener.class.getName(), listener.getClass()));
+        assertEquals(0, context.getAllServices().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <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-annotations</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Thermostat Annotations</name>
+  <url>${project.parent.url}</url>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>com.redhat.thermostat.annotations</Bundle-SymbolicName>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Export-Package>
+              com.redhat.thermostat.annotations,
+            </Export-Package>
+            <Private-Package>
+              com.redhat.thermostat.annotations.internal,
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <!-- dont try to run the annotation process on this module
+               it will pick up the service file and try to run the (not yet
+               compiled) annotation processor and fail mysteriously -->
+          <compilerArgument>-proc:none</compilerArgument>
+        </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+
+
+  <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>
+  </dependencies>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/src/main/java/com/redhat/thermostat/annotations/ExtensionPoint.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a point where the functionality can be extended by a plugin.
+ * To register the plugin, register a new instance of the class annotated by
+ * {@link ExtensionPoint} as an OSGi service.
+ * <p>
+ * To register a class as an OSGi service, use
+ * {@link BundleContext#registerService}.
+ * <p>
+ * This is the whiteboard pattern.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+public @interface ExtensionPoint {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/src/main/java/com/redhat/thermostat/annotations/Service.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that the annotated class is a service available through OSGi.
+ * Clients can obtain an instance of this service and use it directly.
+ * <p>
+ * An instance of this service (if one is registered) can be obtained using
+ * {@link BundleContext#getService(ServiceReference)} or
+ * {@link OSGIUtils#getService(Class)}.
+ * <p>
+ * This does not infer any behaviour on a class; this is for documentation
+ * purposes only.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+public @interface Service {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/src/main/java/com/redhat/thermostat/annotations/internal/AnnotationProcessor.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.annotations.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * An annotation processor that runs and finds @Service and @ExtensionPoint
+ * annotations. A list  of classes using these annotations are written to
+ * a <code>META-INF/thermostat/plugin-docs.xml<code> file.
+ */
+@SupportedAnnotationTypes("com.redhat.thermostat.*")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class AnnotationProcessor extends AbstractProcessor {
+
+    private enum ExposedAs { EXTENSION_POINT, SERVICE }
+
+    private boolean firstRound = false;
+
+    @Override
+    public synchronized void init(ProcessingEnvironment processingEnv) {
+        super.init(processingEnv);
+        firstRound = true;
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        if (!firstRound) {
+            return false;
+        }
+
+        firstRound = false;
+
+        processingEnv.getMessager().printMessage(Kind.NOTE, "Searching for Service and ExtensionPoint annotations");
+
+        List<PluginPointInformation> points = findPluginPoints(annotations, roundEnv);
+
+        processingEnv.getMessager().printMessage(Kind.NOTE, "found " + points.size() + " classes useful for plugins");
+
+        Element[] sourceElements = new Element[points.size()];
+        for (int i = 0; i < points.size(); i++) {
+            sourceElements[i] = points.get(i).annotatedClass;
+        }
+
+        if (points.size() > 0) {
+            try {
+                FileObject filer = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT,
+                        "",
+                        "META-INF" + File.separator + "thermostat" + File.separator + "plugin-docs.xml",
+                        sourceElements);
+                try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(filer.openOutputStream(), "UTF-8"))) {
+                    writeXml(writer, points);
+                }
+            } catch (IOException e) {
+                processingEnv.getMessager().printMessage(Kind.ERROR, "Error writing to docs file: " + e.getMessage());
+            }
+        }
+
+        return false;
+    }
+
+    private List<PluginPointInformation> findPluginPoints(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+        List<PluginPointInformation> pluginPointInfo = new ArrayList<>();
+
+        for (TypeElement annotation : annotations) {
+            ExposedAs exposedType = null;
+            if (annotation.getSimpleName().toString().contains("Service")) {
+                exposedType = ExposedAs.SERVICE;
+            } else if (annotation.getSimpleName().toString().contains("ExtensionPoint")) {
+                exposedType = ExposedAs.EXTENSION_POINT;
+            } else {
+                processingEnv.getMessager().printMessage(Kind.WARNING, "Unrecognized annotation: " + annotation.getSimpleName());
+                continue;
+            }
+
+            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
+            for (Element element : elements) {
+                TypeElement te = (TypeElement) element;
+                pluginPointInfo.add(new PluginPointInformation(te, exposedType, processingEnv.getElementUtils().getDocComment(te)));
+            }
+        }
+        return pluginPointInfo;
+    }
+
+    private void writeXml(PrintWriter writer, List<PluginPointInformation> points) {
+        writer.println("<?xml?>");
+
+        writer.println("<!-- autogenerated by " + this.getClass().getName() + " -->");
+
+        for (PluginPointInformation info: points) {
+            String tag = info.exposedAs == ExposedAs.SERVICE ? "service" : "extension-point";
+
+            writer.println("  <" + tag + ">");
+            writer.println("    <name>" + info.annotatedClass.getQualifiedName() + "</name>");
+            if (info.javadoc != null) {
+                writer.println("    <doc><![CDATA[");
+                writer.println(info.javadoc);
+                writer.println("]]></doc>");
+            }
+            writer.println("  </" + tag + ">");
+        }
+    }
+
+    private static class PluginPointInformation {
+        private TypeElement annotatedClass;
+        private ExposedAs exposedAs;
+        private String javadoc;
+
+        public PluginPointInformation(TypeElement annotatedClass, ExposedAs exposedAs, String javadoc) {
+            this.annotatedClass = annotatedClass;
+            this.exposedAs = exposedAs;
+            this.javadoc = javadoc;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,2 @@
+# This jar provides the following annotation processors:
+com.redhat.thermostat.annotations.internal.AnnotationProcessor
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/annotations/src/test/java/com/redhat/thermostat/annotations/internal/AnnotationProcessorTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.annotations.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.annotations.ExtensionPoint;
+import com.redhat.thermostat.annotations.Service;
+
+public class AnnotationProcessorTest {
+
+	private ProcessingEnvironment processingEnv;
+	private RoundEnvironment roundEnv;
+	private FileObject procesorOutputFile;
+	private Messager messager;
+	private Elements elementUtils;
+
+	private static final String AUTO_GENERATED_COMMENT = "<!-- autogenerated by " + AnnotationProcessor.class.getName() + " -->";
+
+	@Before
+	public void setUp() throws IOException {
+		procesorOutputFile = mock(FileObject.class);
+		Filer filer = mock(Filer.class);
+		when(filer.createResource(
+				eq(StandardLocation.CLASS_OUTPUT),
+				eq(""),
+				eq("META-INF/thermostat/plugin-docs.xml"),
+				any(Element.class)))
+			.thenReturn(procesorOutputFile);
+
+		messager = mock(Messager.class);
+
+		elementUtils = mock(Elements.class);
+
+		processingEnv = mock(ProcessingEnvironment.class);
+		when(processingEnv.getFiler()).thenReturn(filer);
+		when(processingEnv.getMessager()).thenReturn(messager);
+        when(processingEnv.getElementUtils()).thenReturn(elementUtils);
+
+        roundEnv = mock(RoundEnvironment.class);
+	}
+
+	@Test
+	public void testNoAnnotationProducesNoFile() {
+		Set<? extends TypeElement> annotations = new TreeSet<>();
+
+		AnnotationProcessor processor = new AnnotationProcessor();
+		processor.init(processingEnv);
+		processor.process(annotations, roundEnv);
+
+		verifyZeroInteractions(procesorOutputFile);
+	}
+
+	@Test
+    public void testProcessOnServiceClass() throws IOException {
+		final String CLASS_NAME = "c.r.t.annotations.Test";
+		final String JAVADOC = "some javadoc";
+		final String ANNOTATION_CLASS_NAME = Service.class.getName();
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        when(procesorOutputFile.openOutputStream()).thenReturn(out);
+
+        TypeElement serviceAnnotation = mock(TypeElement.class);
+        Name serviceAnnotationName = mock(Name.class);
+        when(serviceAnnotationName.toString()).thenReturn(ANNOTATION_CLASS_NAME);
+        when(serviceAnnotation.getSimpleName()).thenReturn(serviceAnnotationName);
+
+        TypeElement annotatedClass = mock(TypeElement.class);
+        Name annotatedClassName = mock(Name.class);
+        when(annotatedClass.getQualifiedName()).thenReturn(annotatedClassName);
+        when(annotatedClassName.toString()).thenReturn(CLASS_NAME);
+
+        Set<TypeElement> annotations = new HashSet();
+        annotations.add(serviceAnnotation);
+
+        Set annotatedClasses = new HashSet();
+        annotatedClasses.add(annotatedClass);
+
+        when(roundEnv.getElementsAnnotatedWith(serviceAnnotation)).thenReturn(annotatedClasses);
+
+        when(elementUtils.getDocComment(annotatedClass)).thenReturn(JAVADOC);
+
+        AnnotationProcessor processor = new AnnotationProcessor();
+        processor.init(processingEnv);
+        processor.process(annotations, roundEnv);
+
+        String actualFileContents = out.toString("UTF-8");
+        String expectedFileContents = "<?xml?>\n"
+        		+ AUTO_GENERATED_COMMENT + "\n"
+                + "  <service>\n"
+                + "    <name>" + CLASS_NAME + "</name>\n"
+                + "    <doc><![CDATA[\n"
+                + JAVADOC + "\n"
+                + "]]></doc>\n"
+                + "  </service>\n"
+                + "";
+
+        assertEquals(expectedFileContents, actualFileContents);
+    }
+
+	@Test
+    public void testProcessOnExtensionPointClass() throws IOException {
+		final String CLASS_NAME = "c.r.t.annotations.Test";
+		final String JAVADOC = "some javadoc";
+		final String ANNOTATION_CLASS_NAME = ExtensionPoint.class.getName();
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        when(procesorOutputFile.openOutputStream()).thenReturn(out);
+
+        TypeElement serviceAnnotation = mock(TypeElement.class);
+        Name serviceAnnotationName = mock(Name.class);
+        when(serviceAnnotationName.toString()).thenReturn(ANNOTATION_CLASS_NAME);
+        when(serviceAnnotation.getSimpleName()).thenReturn(serviceAnnotationName);
+
+        TypeElement annotatedClass = mock(TypeElement.class);
+        Name annotatedClassName = mock(Name.class);
+        when(annotatedClass.getQualifiedName()).thenReturn(annotatedClassName);
+        when(annotatedClassName.toString()).thenReturn(CLASS_NAME);
+
+        Set<TypeElement> annotations = new HashSet();
+        annotations.add(serviceAnnotation);
+
+        Set annotatedClasses = new HashSet();
+        annotatedClasses.add(annotatedClass);
+
+        when(roundEnv.getElementsAnnotatedWith(serviceAnnotation)).thenReturn(annotatedClasses);
+
+        when(elementUtils.getDocComment(annotatedClass)).thenReturn(JAVADOC);
+
+        AnnotationProcessor processor = new AnnotationProcessor();
+        processor.init(processingEnv);
+        processor.process(annotations, roundEnv);
+
+        String actualFileContents = out.toString("UTF-8");
+        String expectedFileContents = "<?xml?>\n"
+        		+ AUTO_GENERATED_COMMENT + "\n"
+                + "  <extension-point>\n"
+                + "    <name>" + CLASS_NAME + "</name>\n"
+                + "    <doc><![CDATA[\n"
+                + JAVADOC + "\n"
+                + "]]></doc>\n"
+                + "  </extension-point>\n"
+                + "";
+
+        assertEquals(expectedFileContents, actualFileContents);
+    }
+
+}
--- a/bundles/pom.xml	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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	Tue Jan 15 23:00:52 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/client/cli/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -99,18 +99,12 @@
     <dependency>
       <groupId>org.osgi</groupId>
       <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
     </dependency>
-    <!-- Only temporary dependency -->
     <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-vm-cpu-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <!-- Only temporary dependency -->
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-vm-memory-common</artifactId>
-      <version>${project.version}</version>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
     </dependency>
   </dependencies>
 
@@ -125,17 +119,15 @@
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Bundle-Activator>com.redhat.thermostat.client.cli.internal.Activator</Bundle-Activator>
             <Bundle-SymbolicName>com.redhat.thermostat.client.cli</Bundle-SymbolicName>
+            <Export-Package>
+              com.redhat.thermostat.client.cli,
+            </Export-Package>
             <Private-Package>
               META_INF.services,
               com.redhat.thermostat.client.cli.internal,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
-            <!-- Remove this later -->
-            <Import-Package>
-              *,com.redhat.thermostat.vm.cpu.common;resolution:=optional,
-              com.redhat.thermostat.vm.memory.common;resolution:=optional
-            </Import-Package>
           </instructions>
         </configuration>
       </plugin>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/VMStatPrintDelegate.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.client.cli;
+
+import java.util.List;
+
+import com.redhat.thermostat.common.Ordered;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+
+/**
+ * This interface should be implemented by plug-ins that would like to
+ * contribute data to the output of the vm-stat command.
+ */
+public interface VMStatPrintDelegate extends Ordered {
+    
+    /**
+     * Returns statistics gathered by this plug-in newer than the specified
+     * time stamp.
+     * @param ref - the VM whose statistics to return
+     * @param timeStampSince - the earliest time stamp to return statistics for
+     * @return a list of statistics newer than the time stamp
+     */
+    public List<? extends TimeStampedPojo> getLatestStats(VmRef ref, long timeStampSince);
+    
+    /**
+     * Returns header names for columns this plug-in wishes to add to the 
+     * vm-stat command.
+     * @param stat - the first stat returned by {@link #getLatestStats(VmRef, long)}
+     * @return a list of column headers to append to vm-stat output
+     */
+    public List<String> getHeaders(TimeStampedPojo stat);
+    
+    /**
+     * Returns a row of data for the specified statistic that corresponds to
+     * the columns returned by {@link #getHeaders(TimeStampedPojo)}.
+     * @param stat - the statistic to generate output for
+     * @return a row of text for this statistic separated by column
+     */
+    public List<String> getStatRow(TimeStampedPojo stat);
+    
+}
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Tue Jan 15 23:04:57 2013 +0100
@@ -70,8 +70,6 @@
     COLUMN_HEADER_VM_NAME,
     COLUMN_HEADER_VM_STATUS,
     COLUMN_HEADER_TIME,
-    COLUMN_HEADER_CPU_PERCENT,
-    COLUMN_HEADER_MEMORY_PATTERN,
 
     VM_STOP_TIME_RUNNING,
     VM_STATUS_ALIVE,
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMStatCommand.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMStatCommand.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -37,71 +37,81 @@
 package com.redhat.thermostat.client.cli.internal;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
 import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.OrderedComparator;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.HostVMArguments;
 import com.redhat.thermostat.common.cli.SimpleCommand;
 import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
-import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
 public class VMStatCommand extends SimpleCommand {
 
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
     private static final Logger log = LoggingUtils.getLogger(VMStatCommand.class);
-
     private static final String CMD_NAME = "vm-stat";
-
-    private OSGIUtils serviceProvider;
-
+    
+    private List<VMStatPrintDelegate> delegates;
+    private BundleContext context;
+    
     public VMStatCommand() {
-        this(OSGIUtils.getInstance());
+        this(FrameworkUtil.getBundle(VMStatCommand.class).getBundleContext());
     }
 
-    VMStatCommand(OSGIUtils serviceProvider) {
-        this.serviceProvider = serviceProvider;
+    VMStatCommand(BundleContext context) {
+        this.context = context;
+        delegates = new CopyOnWriteArrayList<>();
+        ServiceTracker tracker = new ServiceTracker(context, VMStatPrintDelegate.class.getName(), null) {
+
+            public Object addingService(ServiceReference reference) {
+                VMStatPrintDelegate delegate = (VMStatPrintDelegate) super.addingService(reference);
+                delegates.add(delegate);
+                return delegate;
+            };
+
+            public void removedService(ServiceReference reference, Object service) {
+                delegates.remove(service);
+                super.removedService(reference, service);
+            };
+
+        };
+        tracker.open();
     }
 
     @Override
     public void run(final CommandContext ctx) throws CommandException {
-        VmCpuStatDAO vmCpuStatDAO = serviceProvider.getServiceAllowNull(VmCpuStatDAO.class);
-        if (vmCpuStatDAO == null) {
-            throw new CommandException(translator.localize(LocaleResources.VM_CPU_SERVICE_NOT_AVAILABLE));
-        }
-
-        VmMemoryStatDAO vmMemoryStatDAO = serviceProvider.getServiceAllowNull(VmMemoryStatDAO.class);
-        if (vmMemoryStatDAO == null) {
-            throw new CommandException(translator.localize(LocaleResources.VM_MEMORY_SERVICE_NOT_AVAILABLE));
-        }
-
         HostVMArguments hostVMArgs = new HostVMArguments(ctx.getArguments());
         VmRef vm = hostVMArgs.getVM();
-        final VMStatPrinter statPrinter = new VMStatPrinter(vm, vmCpuStatDAO, vmMemoryStatDAO, ctx.getConsole().getOutput());
+        // Pass a copy of the delegates list to the printer
+        final VMStatPrinter statPrinter = new VMStatPrinter(vm, new ArrayList<>(delegates), ctx.getConsole().getOutput());
         statPrinter.printStats();
         boolean continuous = ctx.getArguments().hasArgument("continuous");
         if (continuous) {
             startContinuousStats(ctx, statPrinter);
         }
-
-        serviceProvider.ungetService(VmMemoryStatDAO.class, vmMemoryStatDAO);
-        serviceProvider.ungetService(VmCpuStatDAO.class, vmCpuStatDAO);
     }
 
     private void startContinuousStats(final CommandContext ctx, final VMStatPrinter statPrinter) {
 
         final CountDownLatch latch = new CountDownLatch(1);
-        ApplicationService appSvc = serviceProvider.getService(ApplicationService.class);
+        ServiceReference ref = context.getServiceReference(ApplicationService.class.getName());
+        ApplicationService appSvc = (ApplicationService) context.getService(ref);
         Timer timer = appSvc.getTimerFactory().createTimer();
         timer.setDelay(1);
         timer.setInitialDelay(1);
@@ -133,6 +143,8 @@
         } catch (InterruptedException e) {
             // Return immediately.
         }
+        
+        context.ungetService(ref);
     }
 
     @Override
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMStatPrinter.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/VMStatPrinter.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -38,70 +38,102 @@
 
 import java.io.PrintStream;
 import java.text.DateFormat;
-import java.text.DecimalFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
-import com.redhat.thermostat.common.Size;
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.common.OrderedComparator;
 import com.redhat.thermostat.common.cli.TableRenderer;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.storage.model.TimeStampedPojo;
 import com.redhat.thermostat.storage.model.TimeStampedPojoComparator;
 import com.redhat.thermostat.storage.model.TimeStampedPojoCorrelator;
-import com.redhat.thermostat.storage.model.VmCpuStat;
-import com.redhat.thermostat.storage.model.VmMemoryStat;
-import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
-import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
 class VMStatPrinter {
 
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
-    private static final String CPU_PERCENT = translator.localize(LocaleResources.COLUMN_HEADER_CPU_PERCENT);
     private static final String TIME = translator.localize(LocaleResources.COLUMN_HEADER_TIME);
 
     private VmRef vm;
-    private VmCpuStatDAO vmCpuStatDAO;
-    private VmMemoryStatDAO vmMemoryStatDAO;
+    private List<VMStatPrintDelegate> delegates;
     private PrintStream out;
-    private TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(2);
+    private TimeStampedPojoCorrelator correlator;
     private TableRenderer table;
-    private int numSpaces;
+    private int numCols;
+    private Map<VMStatPrintDelegate, DelegateInfo> delegateInfo;
 
-    private long lastCpuStatTimeStamp = Long.MIN_VALUE;
-    private long lastMemoryStatTimeStamp = Long.MIN_VALUE;
-
-    VMStatPrinter(VmRef vm, VmCpuStatDAO vmCpuStatDAO, VmMemoryStatDAO vmMemoryStatDAO, PrintStream out) {
+    VMStatPrinter(VmRef vm, List<VMStatPrintDelegate> delegates, PrintStream out) {
         this.vm = vm;
-        this.vmCpuStatDAO = vmCpuStatDAO;
-        this.vmMemoryStatDAO = vmMemoryStatDAO;
+        this.delegates = delegates;
         this.out = out;
+        int numDelegates = delegates.size();
+        this.delegateInfo = new HashMap<>();
+        this.correlator = new TimeStampedPojoCorrelator(numDelegates);
+        
+        // Sort the delegates list
+        Collections.sort(delegates, new OrderedComparator<>());
+        
+        for (VMStatPrintDelegate delegate : delegates) {
+            DelegateInfo info = new DelegateInfo();
+            info.lastTimeStamp = Long.MIN_VALUE;
+            delegateInfo.put(delegate, info);
+        }
     }
 
     void printStats() {
-        List<VmCpuStat> cpuStats = vmCpuStatDAO.getLatestVmCpuStats(vm, lastCpuStatTimeStamp);
-        List<VmMemoryStat> memStats = vmMemoryStatDAO.getLatestVmMemoryStats(vm, lastMemoryStatTimeStamp);
-
-        lastCpuStatTimeStamp = getLatestTimeStamp(lastCpuStatTimeStamp, cpuStats);
-        lastMemoryStatTimeStamp = getLatestTimeStamp(lastMemoryStatTimeStamp, memStats);
-
-        printStats(cpuStats, memStats);
+        List<List<? extends TimeStampedPojo>> allStats = new ArrayList<>();
+        List<String> allHeaders = new ArrayList<>();
+        allHeaders.add(TIME);
+        
+        // Copy since we can remove elements in loop body
+        List<VMStatPrintDelegate> delegatesCopy = new ArrayList<>(delegates);
+        for (VMStatPrintDelegate delegate : delegatesCopy) {
+            long timeStamp = delegateInfo.get(delegate).lastTimeStamp;
+            List<? extends TimeStampedPojo> latestStats = delegate.getLatestStats(vm, timeStamp);
+            if (latestStats == null || latestStats.isEmpty()) {
+                // Skipping delegate
+                delegates.remove(delegate);
+            }
+            else {
+                List<String> headers = delegate.getHeaders(latestStats.get(0));
+                if (headers == null || headers.isEmpty()) {
+                    // Skipping delegate
+                    delegates.remove(delegate);
+                }
+                else {
+                    DelegateInfo info = delegateInfo.get(delegate);
+                    info.colsPerDelegate = headers.size();
+                    allHeaders.addAll(headers);
+                    allStats.add(latestStats);
+                    info.lastTimeStamp = getLatestTimeStamp(timeStamp, latestStats);
+                }
+            }
+        }
+        
+        printStats(allStats, allHeaders);
     }
 
     void printUpdatedStats() {
         correlator.clear();
-        List<VmCpuStat> cpuStats = vmCpuStatDAO.getLatestVmCpuStats(vm, lastCpuStatTimeStamp);
-        List<VmMemoryStat> memStats = vmMemoryStatDAO.getLatestVmMemoryStats(vm, lastMemoryStatTimeStamp);
+        
+        List<List<? extends TimeStampedPojo>> allStats = new ArrayList<>();
+        for (VMStatPrintDelegate delegate : delegates) {
+            DelegateInfo info = delegateInfo.get(delegate);
+            List<? extends TimeStampedPojo> latestStats = delegate.getLatestStats(vm, info.lastTimeStamp);
+            allStats.add(latestStats);
+            info.lastTimeStamp = getLatestTimeStamp(info.lastTimeStamp, latestStats);
+        }
 
-        lastCpuStatTimeStamp = getLatestTimeStamp(lastCpuStatTimeStamp, cpuStats);
-        lastMemoryStatTimeStamp = getLatestTimeStamp(lastMemoryStatTimeStamp, memStats);
-
-        correlate(cpuStats, memStats);
+        correlate(allStats);
         printUpdatedStatsImpl();
     }
 
@@ -113,103 +145,79 @@
         }
     }
 
-    private void printStats(List<VmCpuStat> cpuStats, List<VmMemoryStat> memStats) {
-        correlate(cpuStats, memStats);
-        numSpaces = getNumSpaces(memStats);
-        int numColumns = numSpaces + 2;
-        table = new TableRenderer(numColumns);
-        printHeaders(memStats, numSpaces, numColumns, table);
+    private void printStats(List<List<? extends TimeStampedPojo>> allStats, List<String> headers) {
+        correlate(allStats);
+        numCols = headers.size();
+        table = new TableRenderer(numCols);
+        printHeaders(table, headers);
         printUpdatedStatsImpl();
     }
 
-    private void printStats(int numSpaces, TableRenderer table, Iterator<TimeStampedPojoCorrelator.Correlation> i) {
-
-        TimeStampedPojoCorrelator.Correlation correlation = i.next();
+    private void printStats(TableRenderer table, Iterator<TimeStampedPojoCorrelator.Correlation> iter) {
+        TimeStampedPojoCorrelator.Correlation correlation = iter.next();
 
-        VmCpuStat cpuStat = (VmCpuStat) correlation.get(0);
-        DecimalFormat format = new DecimalFormat("#0.0");
-        String cpuLoad = cpuStat != null ? format.format(cpuStat.getCpuLoad()) : "";
-
+        String[] line = new String[numCols];
         DateFormat dateFormat = DateFormat.getTimeInstance();
         String time = dateFormat.format(new Date(correlation.getTimeStamp()));
-
-        String[] memoryUsage = getMemoryUsage((VmMemoryStat) correlation.get(1), numSpaces);
-
-        String[] line = new String[numSpaces + 2];
-        System.arraycopy(memoryUsage, 0, line, 2, numSpaces);
         line[0] = time;
-        line[1] = cpuLoad;
+        
+        int off = 1; // time is first index
+        for (int i = 0; i < delegates.size(); i++) {
+            TimeStampedPojo stat = correlation.get(i);
+            VMStatPrintDelegate delegate = delegates.get(i);
+            if (stat == null) {
+                // Fill with blanks
+                DelegateInfo info = delegateInfo.get(delegate);
+                Arrays.fill(line, off, off + info.colsPerDelegate, "");
+                off += info.colsPerDelegate;
+            }
+            else {
+                List<String> data = delegate.getStatRow(stat);
+                if (data == null) {
+                    throw new NullPointerException("Returned null stat row");
+                }
+                else if (data.size() != delegateInfo.get(delegate).colsPerDelegate) {
+                    throw new IllegalStateException("Delegate "
+                            + delegate.toString() + " provided "
+                            + delegateInfo.get(delegate).colsPerDelegate
+                            + " column headers, but only " + data.size()
+                            + " stat row values");
+                }
+                else {
+                    System.arraycopy(data.toArray(), 0, line, off, data.size());
+                    off += data.size();
+                }
+            }
+        }
+        
         table.printLine(line);
     }
 
-    private void printHeaders(List<VmMemoryStat> memStats, int numSpaces, int numColumns, TableRenderer table) {
-        String[] spacesNames = getSpacesNames(memStats, numSpaces);
-        String[] headers = new String[numColumns];
-        headers[0] = TIME;
-        headers[1] = CPU_PERCENT;
-        System.arraycopy(spacesNames, 0, headers, 2, numSpaces);
-        table.printLine(headers);
-    }
-
-    private String[] getMemoryUsage(VmMemoryStat vmMemoryStat, int numSpaces) {
-        String[] memoryUsage = new String[numSpaces];
-        if (vmMemoryStat == null) {
-            Arrays.fill(memoryUsage, "");
-            return memoryUsage;
-        }
-        int i = 0;
-        for (VmMemoryStat.Generation gen : vmMemoryStat.getGenerations()) {
-            for (VmMemoryStat.Space space : gen.getSpaces()) {
-                memoryUsage[i] = Size.bytes(space.getUsed()).toString();
-                i++;
-            }
-        }
-        return memoryUsage;
+    private void printHeaders(TableRenderer table, List<String> headers) {
+        table.printLine(headers.toArray(new String[headers.size()]));
     }
 
-    private String[] getSpacesNames(List<VmMemoryStat> memStats, int numSpaces) {
-        if (numSpaces < 1) {
-            return new String[0];
-        }
-        String[] spacesNames = new String[numSpaces];
-        VmMemoryStat stat = memStats.get(0);
-        int i = 0;
-        for (VmMemoryStat.Generation gen : stat.getGenerations()) {
-            for (VmMemoryStat.Space space : gen.getSpaces()) {
-                spacesNames[i] = translator.localize(LocaleResources.COLUMN_HEADER_MEMORY_PATTERN, space.getName());
-                i++;
+    private void correlate(List<List<? extends TimeStampedPojo>> allStats) {
+        int count = 0;
+        for (List<? extends TimeStampedPojo> stats : allStats) {
+            for(TimeStampedPojo cpuStat : stats) {
+                correlator.add(count, cpuStat);
             }
-        }
-        return spacesNames;
-    }
-
-    private int getNumSpaces(List<VmMemoryStat> memStats) {
-        if (memStats.size() < 1) {
-            return 0;
-        }
-        VmMemoryStat stat = memStats.get(0);
-        int numSpaces = 0;
-        for (VmMemoryStat.Generation gen : stat.getGenerations()) {
-            numSpaces += gen.getSpaces().length;
-        }
-        return numSpaces;
-    }
-
-    private void correlate(List<VmCpuStat> cpuStats, List<VmMemoryStat> memStats) {
-        for(VmCpuStat cpuStat : cpuStats) {
-            correlator.add(0, cpuStat);
-        }
-        for (VmMemoryStat memStat : memStats) {
-            correlator.add(1, memStat);
+            count++;
         }
     }
 
     void printUpdatedStatsImpl() {
         Iterator<TimeStampedPojoCorrelator.Correlation> iterator = correlator.iterator();
         while (iterator.hasNext()) {
-            printStats(numSpaces, table, iterator);
+            printStats(table, iterator);
         }
         table.render(out);
     }
+    
+    private static class DelegateInfo {
+        int colsPerDelegate;
+        long lastTimeStamp;
+    }
 
 }
--- a/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Tue Jan 15 23:04:57 2013 +0100
@@ -28,8 +28,6 @@
 COLUMN_HEADER_VM_NAME = VM_NAME
 COLUMN_HEADER_VM_STATUS = STATUS
 COLUMN_HEADER_TIME = TIME
-COLUMN_HEADER_CPU_PERCENT = %CPU
-COLUMN_HEADER_MEMORY_PATTERN = MEM.{0}
 
 VM_STOP_TIME_RUNNING = <Running>
 VM_STATUS_ALIVE = RUNNING
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ActivatorTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ActivatorTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -38,6 +38,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -58,10 +59,15 @@
 
     @Test
     public void testCommandsRegistered() throws Exception {
-        // Need to mock FrameworkUtil to avoid NPE in ShellCommand's no-arg constructor
+        // Need to mock FrameworkUtil to avoid NPE in ShellCommand and
+        // VMStatCommand's no-arg constructors
         PowerMockito.mockStatic(FrameworkUtil.class);
         Bundle mockBundle = mock(Bundle.class);
         when(FrameworkUtil.getBundle(ShellCommand.class)).thenReturn(mockBundle);
+        when(FrameworkUtil.getBundle(VMStatCommand.class)).thenReturn(mockBundle);
+        // When we call createFilter, we need a real return value
+        when(FrameworkUtil.createFilter(anyString())).thenCallRealMethod();
+
         StubBundleContext ctx = new StubBundleContext();
         when(mockBundle.getBundleContext()).thenReturn(ctx);
         
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VmStatCommandTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VmStatCommandTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -40,11 +40,15 @@
 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.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -59,26 +63,21 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
 import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.SimpleArguments;
-import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.storage.model.VmCpuStat;
-import com.redhat.thermostat.storage.model.VmMemoryStat;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+import com.redhat.thermostat.test.StubBundleContext;
 import com.redhat.thermostat.test.TestCommandContextFactory;
 import com.redhat.thermostat.test.TestTimerFactory;
-import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
-import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
 public class VmStatCommandTest {
-
     private static Locale defaultLocale;
     private static TimeZone defaultTimeZone;
+    private static int NUM_ROWS = 3;
 
     @BeforeClass
     public static void setUpClass() {
@@ -94,34 +93,62 @@
         Locale.setDefault(defaultLocale);
     }
 
-    private VMStatCommand cmd;
-    private VmCpuStatDAO vmCpuStatDAO;
+    private VMStatPrintDelegate[] delegates;
     private TestCommandContextFactory cmdCtxFactory;
-    private VmMemoryStatDAO vmMemoryStatDAO;
     private TestTimerFactory timerFactory;
+    private ApplicationService appSvc;
 
     @Before
     public void setUp() {
+        delegates = new VMStatPrintDelegate[2];
+        final String[][] headers = {
+                { "FIRST", "SECOND", "THIRD" }, 
+                { "FOURTH", "FIFTH" } };
+        
+        final String[][][] rows = {
+                {
+                    { "1", "2", "3" },
+                    { "6", "7", "8" },
+                    { "11", "12", "13" },
+                },
+                {
+                    { "4", "5" },
+                    { "9", "10" },
+                    { "14", "15" }
+                }
+        };
         timerFactory = new TestTimerFactory();
-        ApplicationService appSvc = mock(ApplicationService.class);
+        appSvc = mock(ApplicationService.class);
         when(appSvc.getTimerFactory()).thenReturn(timerFactory);
         setupCommandContextFactory();
-
-        setupDAOs();
-
-        OSGIUtils serviceProvider = mock(OSGIUtils.class);
-        when(serviceProvider.getServiceAllowNull(VmCpuStatDAO.class)).thenReturn(vmCpuStatDAO);
-        when(serviceProvider.getServiceAllowNull(VmMemoryStatDAO.class)).thenReturn(vmMemoryStatDAO);
-        when(serviceProvider.getService(ApplicationService.class)).thenReturn(appSvc);
-
-        cmd = new VMStatCommand(serviceProvider);
+        
+        delegates[0] = mockDelegate(headers[0], rows[0]);
+        delegates[1] = mockDelegate(headers[1], rows[1]);
+    }
+    
+    private VMStatPrintDelegate mockDelegate(String[] headers, String[][] data) {
+        VMStatPrintDelegate delegate = mock(VMStatPrintDelegate.class);
+        List<TimeStampedPojo> stats = new ArrayList<>();
+        for (int i = 0; i < NUM_ROWS; i++) {
+            TimeStampedPojo stat = mock(TimeStampedPojo.class);
+            when(stat.getTimeStamp()).thenReturn(i * 1000L); // Increment by one second
+            stats.add(stat);
+        }
+        
+        // Need this syntax due to generics
+        doReturn(stats).when(delegate).getLatestStats(any(VmRef.class), eq(Long.MIN_VALUE));
+        when(delegate.getHeaders(stats.get(0))).thenReturn(Arrays.asList(headers));
+        for (int i = 0; i < data.length; i++) {
+            List<String> row = Arrays.asList(data[i]);
+            doReturn(row).when(delegate).getStatRow(eq(stats.get(i)));
+        }
+        
+        return delegate;
     }
 
     @After
     public void tearDown() {
-        vmCpuStatDAO = null;
         cmdCtxFactory = null;
-        cmd = null;
         timerFactory = null;
     }
 
@@ -129,116 +156,62 @@
         cmdCtxFactory = new TestCommandContextFactory();
     }
 
-    private void setupDAOs() {
-        vmCpuStatDAO = mock(VmCpuStatDAO.class);
-        int vmId = 234;
-        HostRef host = new HostRef("123", "dummy");
-        VmRef vm = new VmRef(host, 234, "dummy");
-        VmCpuStat cpustat1 = new VmCpuStat(2, vmId, 65);
-        VmCpuStat cpustat2 = new VmCpuStat(3, vmId, 70);
-        List<VmCpuStat> cpuStats = Arrays.asList(cpustat1, cpustat2);
-        List<VmCpuStat> cpuStats2 = Collections.emptyList();
-        when(vmCpuStatDAO.getLatestVmCpuStats(vm, Long.MIN_VALUE)).thenReturn(cpuStats).thenReturn(cpuStats2);
-
-        VmMemoryStat.Space space1_1_1 = newSpace("space1", 123456, 12345, 1, 0);
-        VmMemoryStat.Space space1_1_2 = newSpace("space2", 123456, 12345, 1, 0);
-        VmMemoryStat.Space[] spaces1_1 = new VmMemoryStat.Space[] { space1_1_1, space1_1_2 };
-        VmMemoryStat.Generation gen1_1 = newGeneration("gen1", "col1", 123456, 12345, spaces1_1);
-
-        VmMemoryStat.Space space1_2_1 = newSpace("space3", 123456, 12345, 1, 0);
-        VmMemoryStat.Space space1_2_2 = newSpace("space4", 123456, 12345, 1, 0);
-        VmMemoryStat.Space[] spaces1_2 = new VmMemoryStat.Space[] { space1_2_1, space1_2_2 };
-        VmMemoryStat.Generation gen1_2 = newGeneration("gen2", "col1", 123456, 12345, spaces1_2);
-
-        VmMemoryStat.Generation[] gens1 = new VmMemoryStat.Generation[] { gen1_1, gen1_2 };
-
-        VmMemoryStat memStat1 = new VmMemoryStat(1, vmId, gens1);
-
-        VmMemoryStat.Space space2_1_1 = newSpace("space1", 123456, 12345, 2, 0);
-        VmMemoryStat.Space space2_1_2 = newSpace("space2", 123456, 12345, 2, 0);
-        VmMemoryStat.Space[] spaces2_1 = new VmMemoryStat.Space[] { space2_1_1, space2_1_2 };
-        VmMemoryStat.Generation gen2_1 = newGeneration("gen1", "col1", 123456, 12345, spaces2_1);
-
-        VmMemoryStat.Space space2_2_1 = newSpace("space3", 123456, 12345, 3, 0);
-        VmMemoryStat.Space space2_2_2 = newSpace("space4", 123456, 12345, 4, 0);
-        VmMemoryStat.Space[] spaces2_2 = new VmMemoryStat.Space[] { space2_2_1, space2_2_2 };
-        VmMemoryStat.Generation gen2_2 = newGeneration("gen2", "col1", 123456, 12345, spaces2_2);
-
-        VmMemoryStat.Generation[] gens2 = new VmMemoryStat.Generation[] { gen2_1, gen2_2 };
-
-        VmMemoryStat memStat2 = new VmMemoryStat(2, vmId, gens2);
-
-        VmMemoryStat.Space space3_1_1 = newSpace("space1", 123456, 12345, 4, 0);
-        VmMemoryStat.Space space3_1_2 = newSpace("space2", 123456, 12345, 5, 0);
-        VmMemoryStat.Space[] spaces3_1 = new VmMemoryStat.Space[] { space3_1_1, space3_1_2 };
-        VmMemoryStat.Generation gen3_1 = newGeneration("gen1", "col1", 123456, 12345, spaces3_1);
-
-        VmMemoryStat.Space space3_2_1 = newSpace("space3", 123456, 12345, 6, 0);
-        VmMemoryStat.Space space3_2_2 = newSpace("space4", 123456, 12345, 7, 0);
-        VmMemoryStat.Space[] spaces3_2 = new VmMemoryStat.Space[] { space3_2_1, space3_2_2 };
-        VmMemoryStat.Generation gen3_2 = newGeneration("gen2", "col1", 123456, 12345, spaces3_2);
-
-        VmMemoryStat.Generation[] gens3 = new VmMemoryStat.Generation[] { gen3_1, gen3_2 };
-
-        VmMemoryStat memStat3 = new VmMemoryStat(3, vmId, gens3);
-
-        VmMemoryStat.Space space4_1_1 = newSpace("space1", 123456, 12345, 8, 0);
-        VmMemoryStat.Space space4_1_2 = newSpace("space2", 123456, 12345, 9, 0);
-        VmMemoryStat.Space[] spaces4_1 = new VmMemoryStat.Space[] { space4_1_1, space4_1_2 };
-        VmMemoryStat.Generation gen4_1 = newGeneration("gen4", "col1", 123456, 12345, spaces4_1);
-
-        VmMemoryStat.Space space4_2_1 = newSpace("space3", 123456, 12345, 10, 0);
-        VmMemoryStat.Space space4_2_2 = newSpace("space4", 123456, 12345, 11, 0);
-        VmMemoryStat.Space[] spaces4_2 = new VmMemoryStat.Space[] { space4_2_1, space4_2_2 };
-        VmMemoryStat.Generation gen4_2 = newGeneration("gen4", "col1", 123456, 12345, spaces4_2);
-
-        VmMemoryStat.Generation[] gens4 = new VmMemoryStat.Generation[] { gen4_1, gen4_2 };
-
-        VmMemoryStat memStat4 = new VmMemoryStat(4, vmId, gens4);
-
-        vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
-        when(vmMemoryStatDAO.getLatestVmMemoryStats(vm, Long.MIN_VALUE))
-            .thenReturn(Arrays.asList(memStat1, memStat2, memStat3));
-
-        when(vmMemoryStatDAO.getLatestVmMemoryStats(vm, memStat3.getTimeStamp())).thenReturn(Arrays.asList(memStat4));
-
+    @Test
+    public void testOutput() throws CommandException {
+        StubBundleContext context = new StubBundleContext();
+        context.registerService(VMStatPrintDelegate.class.getName(), delegates[0], null);
+        context.registerService(VMStatPrintDelegate.class.getName(), delegates[1], null);
+        
+        VMStatCommand cmd = new VMStatCommand(context);
+        
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "TIME        FIRST SECOND THIRD FOURTH FIFTH\n"
+                + "12:00:00 AM 1     2      3     4      5\n"
+                + "12:00:01 AM 6     7      8     9      10\n"
+                + "12:00:02 AM 11    12     13    14     15\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
     }
-
-    private Space newSpace(String name, long maxCapacity, long capacity, long used, int index) {
-        VmMemoryStat.Space space = new VmMemoryStat.Space();
-        space.setName(name);
-        space.setMaxCapacity(maxCapacity);
-        space.setCapacity(capacity);
-        space.setUsed(used);
-        space.setIndex(index);
-        return space;
-    }
-
-    private Generation newGeneration(String name, String collector, long maxCapacity, long capacity, Space[] spaces) {
-        VmMemoryStat.Generation gen = new VmMemoryStat.Generation();
-        gen.setName(name);
-        gen.setCollector(collector);
-        gen.setMaxCapacity(capacity);
-        gen.setSpaces(spaces);
-        return gen;
-    }
-
+    
     @Test
-    public void testBasicCPUMemory() throws CommandException {
+    public void testNoDelegates() throws CommandException {
+        StubBundleContext context = new StubBundleContext();
+        VMStatCommand cmd = new VMStatCommand(context);
+        
         SimpleArguments args = new SimpleArguments();
         args.addArgument("vmId", "234");
         args.addArgument("hostId", "123");
         cmd.run(cmdCtxFactory.createContext(args));
-        String expected = "TIME        %CPU MEM.space1 MEM.space2 MEM.space3 MEM.space4\n" +
-                          "12:00:00 AM      1 B        1 B        1 B        1 B\n" +
-                          "12:00:00 AM 65.0 2 B        2 B        3 B        4 B\n" +
-                          "12:00:00 AM 70.0 4 B        5 B        6 B        7 B\n";
+        String expected = "TIME\n";
         assertEquals(expected, cmdCtxFactory.getOutput());
-
     }
 
     @Test
     public void testContinuousMode() throws CommandException {
+        final String[][] data = {
+                { "16", "17", "18" },
+                { "19", "20" }
+        };
+        StubBundleContext context = new StubBundleContext();
+        context.registerService(ApplicationService.class.getName(), appSvc, null);
+        context.registerService(VMStatPrintDelegate.class.getName(), delegates[0], null);
+        context.registerService(VMStatPrintDelegate.class.getName(), delegates[1], null);
+        
+        // Add one more stat
+        TimeStampedPojo stat = mock(TimeStampedPojo.class);
+        // One second after previous timestamps
+        when(stat.getTimeStamp()).thenReturn(3000L);
+        List<TimeStampedPojo> stats = new ArrayList<>();
+        stats.add(stat);
+        
+        doReturn(stats).when(delegates[0]).getLatestStats(any(VmRef.class), eq(2000L));
+        doReturn(stats).when(delegates[1]).getLatestStats(any(VmRef.class), eq(2000L));
+        doReturn(Arrays.asList(data[0])).when(delegates[0]).getStatRow(eq(stat));
+        doReturn(Arrays.asList(data[1])).when(delegates[1]).getStatRow(eq(stat));
+        
+        final VMStatCommand cmd = new VMStatCommand(context);
         
         Thread t = new Thread() {
             public void run() {
@@ -261,10 +234,10 @@
             return;
         }
         assertTrue(timerFactory.isActive());
-        String expected = "TIME        %CPU MEM.space1 MEM.space2 MEM.space3 MEM.space4\n" +
-                          "12:00:00 AM      1 B        1 B        1 B        1 B\n" +
-                          "12:00:00 AM 65.0 2 B        2 B        3 B        4 B\n" +
-                          "12:00:00 AM 70.0 4 B        5 B        6 B        7 B\n";
+        String expected = "TIME        FIRST SECOND THIRD FOURTH FIFTH\n" +
+                "12:00:00 AM 1     2      3     4      5\n" +
+                "12:00:01 AM 6     7      8     9      10\n" +
+                "12:00:02 AM 11    12     13    14     15\n";
         assertEquals(expected, cmdCtxFactory.getOutput());
         assertEquals(1, timerFactory.getDelay());
         assertEquals(1, timerFactory.getInitialDelay());
@@ -273,11 +246,11 @@
 
         timerFactory.getAction().run();
 
-        expected = "TIME        %CPU MEM.space1 MEM.space2 MEM.space3 MEM.space4\n" +
-                   "12:00:00 AM      1 B        1 B        1 B        1 B\n" +
-                   "12:00:00 AM 65.0 2 B        2 B        3 B        4 B\n" +
-                   "12:00:00 AM 70.0 4 B        5 B        6 B        7 B\n" +
-                   "12:00:00 AM 70.0 8 B        9 B        10 B       11 B\n";
+        expected = "TIME        FIRST SECOND THIRD FOURTH FIFTH\n" +
+                "12:00:00 AM 1     2      3     4      5\n" +
+                "12:00:01 AM 6     7      8     9      10\n" +
+                "12:00:02 AM 11    12     13    14     15\n" +
+                "12:00:03 AM 16    17     18    19     20\n";
         assertEquals(expected, cmdCtxFactory.getOutput());
         cmdCtxFactory.setInput(" ");
         try {
@@ -290,18 +263,24 @@
 
     @Test
     public void testName() {
+        StubBundleContext context = new StubBundleContext();
+        VMStatCommand cmd = new VMStatCommand(context);
         assertEquals("vm-stat", cmd.getName());
     }
 
     @Test
     public void testDescAndUsage() {
-        assertNotNull(cmd.getUsage());
+        StubBundleContext context = new StubBundleContext();
+        VMStatCommand cmd = new VMStatCommand(context);
+        assertNotNull(cmd.getDescription());
         assertNotNull(cmd.getUsage());
     }
 
     @Ignore
     @Test
     public void testOptions() {
+        StubBundleContext context = new StubBundleContext();
+        VMStatCommand cmd = new VMStatCommand(context);
         Options options = cmd.getOptions();
         assertNotNull(options);
         assertEquals(3, options.getOptions().size());
@@ -325,9 +304,91 @@
         assertFalse(cont.isRequired());
         assertFalse(cont.hasArg());
     }
+    
+    @Test
+    public void testNoStats() throws CommandException {
+        // Fail stats != null check
+        VMStatPrintDelegate badDelegate = mock(VMStatPrintDelegate.class);
+        when(badDelegate.getLatestStats(any(VmRef.class), anyLong())).thenReturn(null);
+        
+        StubBundleContext context = new StubBundleContext();
+        context.registerService(VMStatPrintDelegate.class, delegates[0], null);
+        context.registerService(VMStatPrintDelegate.class, badDelegate, null);
+        context.registerService(VMStatPrintDelegate.class, delegates[1], null);
+        
+        VMStatCommand cmd = new VMStatCommand(context);
+        
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "TIME        FIRST SECOND THIRD FOURTH FIFTH\n"
+                + "12:00:00 AM 1     2      3     4      5\n"
+                + "12:00:01 AM 6     7      8     9      10\n"
+                + "12:00:02 AM 11    12     13    14     15\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
+    }
+    
+    @Test
+    public void testNoHeaders() throws CommandException {
+        // Pass stats check, but fail headers check
+        VMStatPrintDelegate badDelegate = mock(VMStatPrintDelegate.class);
+        TimeStampedPojo stat = mock(TimeStampedPojo.class);
+        doReturn(Arrays.asList(stat)).when(badDelegate).getLatestStats(any(VmRef.class), anyLong());
+        when(badDelegate.getHeaders(any(TimeStampedPojo.class))).thenReturn(null);
+        
+        StubBundleContext context = new StubBundleContext();
+        context.registerService(VMStatPrintDelegate.class, delegates[0], null);
+        context.registerService(VMStatPrintDelegate.class, badDelegate, null);
+        context.registerService(VMStatPrintDelegate.class, delegates[1], null);
+        
+        VMStatCommand cmd = new VMStatCommand(context);
+        
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "TIME        FIRST SECOND THIRD FOURTH FIFTH\n"
+                + "12:00:00 AM 1     2      3     4      5\n"
+                + "12:00:01 AM 6     7      8     9      10\n"
+                + "12:00:02 AM 11    12     13    14     15\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
+    }
+    
+    @Test
+    public void testUnevenStat() throws CommandException {
+        // Fewer stats than other delegates
+        VMStatPrintDelegate badDelegate = mock(VMStatPrintDelegate.class);
+        TimeStampedPojo stat1 = mock(TimeStampedPojo.class);
+        when(stat1.getTimeStamp()).thenReturn(1000L);
+        TimeStampedPojo stat2 = mock(TimeStampedPojo.class);
+        when(stat2.getTimeStamp()).thenReturn(2000L);
+        doReturn(Arrays.asList(stat1, stat2)).when(badDelegate).getLatestStats(any(VmRef.class), anyLong());
+        when(badDelegate.getHeaders(any(TimeStampedPojo.class))).thenReturn(Arrays.asList("BAD"));
+        when(badDelegate.getStatRow(any(TimeStampedPojo.class))).thenReturn(Arrays.asList("0"));
+        
+        StubBundleContext context = new StubBundleContext();
+        context.registerService(VMStatPrintDelegate.class, delegates[0], null);
+        context.registerService(VMStatPrintDelegate.class, badDelegate, null);
+        context.registerService(VMStatPrintDelegate.class, delegates[1], null);
+        
+        VMStatCommand cmd = new VMStatCommand(context);
+        
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("vmId", "234");
+        args.addArgument("hostId", "123");
+        cmd.run(cmdCtxFactory.createContext(args));
+        String expected = "TIME        FIRST SECOND THIRD BAD FOURTH FIFTH\n"
+                + "12:00:00 AM 1     2      3         4      5\n"
+                + "12:00:01 AM 6     7      8     0   9      10\n"
+                + "12:00:02 AM 11    12     13    0   14     15\n";
+        assertEquals(expected, cmdCtxFactory.getOutput());
+    }
 
     @Test
     public void testStorageRequired() {
+        StubBundleContext context = new StubBundleContext();
+        VMStatCommand cmd = new VMStatCommand(context);
         assertTrue(cmd.isStorageRequired());
     }
 }
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/RequestQueue.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/RequestQueue.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.command;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.command.Request;
 
 /**
@@ -45,6 +46,7 @@
  * and response (if any) are processed asynchronously. An instance of this can
  * be obtained from OSGi.
  */
+@Service
 public interface RequestQueue {
 
     public void putRequest(Request request);
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/InformationService.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/InformationService.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
 import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.dao.Ref;
@@ -46,7 +47,13 @@
  * <p>
  * An {@code InformationService} provides some sort of information about
  * something. Plug-ins should normally implement this as a entry point.
+ * <p>
+ * To provide an implementation of {@link InformationService}, register an
+ * instance of this interface as an OSGi service with the property
+ * {@link Constants#GENERIC_SERVICE_CLASSNAME} set to the name of the Class
+ * that this {@link InformationService} provides information for.
  */
+@ExtensionPoint
 public interface InformationService<T extends Ref> extends Ordered {
     
     /**
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/views/AgentInformationViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/views/AgentInformationViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,12 @@
 
 package com.redhat.thermostat.client.core.views;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
- * Provides an AgentInformationViewProvider
+ * Provides an {@link AgentInformationDisplayView}
  */
+@ExtensionPoint
 public interface AgentInformationViewProvider extends ViewProvider {
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/views/ClientConfigViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/views/ClientConfigViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,12 @@
 
 package com.redhat.thermostat.client.core.views;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * A services that provides {@link ClientConfigurationView}s.
  */
+@ExtensionPoint
 public interface ClientConfigViewProvider extends ViewProvider {
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/views/HostInformationViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/views/HostInformationViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,12 @@
 
 package com.redhat.thermostat.client.core.views;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * This services provides an appropriate {@link HostInformationView}.
  */
+@ExtensionPoint
 public interface HostInformationViewProvider extends ViewProvider {
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/views/VmInformationViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/views/VmInformationViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,12 @@
 
 package com.redhat.thermostat.client.core.views;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * A service that provides a {@link VmInformationView}
  */
+@ExtensionPoint
 public interface VmInformationViewProvider extends ViewProvider {
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ContextAction.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ContextAction.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.client.osgi.service;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * Marker service for context menu actions.
  * <br /><br />
@@ -56,6 +58,7 @@
  * 
  * <strong>Exported entry point</strong>: com.redhat.thermostat.client.osgi.service.ContextAction
  */
+@ExtensionPoint
 public interface ContextAction {
     
     String getName();
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java	Tue Jan 15 23:04:57 2013 +0100
@@ -35,6 +35,8 @@
  */
 package com.redhat.thermostat.client.osgi.service;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * Allows plugins to register menu items.
  * <p>
@@ -42,6 +44,7 @@
  * register a service that implements this class with the property
  * "parentMenu" set to "File".
  */
+@ExtensionPoint
 public interface MenuAction extends ContextAction {
 
     public static enum Type {
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,12 +36,14 @@
 
 package com.redhat.thermostat.client.osgi.service;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.common.dao.VmRef;
 
 /**
  * A context action for VMs
  */
+@ExtensionPoint
 public interface VMContextAction extends ContextAction {
 
     void execute(VmRef referece);
--- a/common/core/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -160,6 +160,12 @@
       <systemPath>${java.home}/../lib/tools.jar</systemPath>
     </dependency>
     <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
     	<groupId>com.redhat.thermostat</groupId>
     	<artifactId>thermostat-keyring</artifactId>
     	<version>${project.version}</version>
--- a/common/core/src/main/java/com/redhat/thermostat/common/ApplicationService.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/ApplicationService.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,6 +38,9 @@
 
 import java.util.concurrent.ExecutorService;
 
+import com.redhat.thermostat.annotations.Service;
+
+@Service
 public interface ApplicationService {
 
     ApplicationCache getApplicationCache();
--- a/common/core/src/main/java/com/redhat/thermostat/common/CommandLoadingBundleActivator.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/CommandLoadingBundleActivator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -45,7 +45,7 @@
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
-/*
+/**
  * Superclass for activators that need to register commands.  The bundle for this
  * activator should contain a META-INF/services/com.redhat.thermostat.common.cli.Command
  * file containing the class names that should be loaded as commands.  If this activator
--- a/common/core/src/main/java/com/redhat/thermostat/common/DbService.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/DbService.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.common;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.ConnectionException;
 
+@Service
 public interface DbService {
 
     /**
--- a/common/core/src/main/java/com/redhat/thermostat/common/TimerFactory.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/TimerFactory.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.common;
 
+/**
+ * An instance of this can be obtained from {@link ApplicationService}.
+ */
 public interface TimerFactory {
 
     Timer createTimer();
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/Command.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/Command.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,6 +38,8 @@
 
 import org.apache.commons.cli.Options;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
+
 /**
  * Represents a command on the command line.
  * <p>
@@ -51,6 +53,7 @@
  * <p>
  * @see CommandRegistry
  */
+@ExtensionPoint
 public interface Command {
 
     public static final String NAME = "COMMAND_NAME";
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,10 +38,12 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.AgentInformation;
 
+@Service
 public interface AgentInfoDAO extends Countable {
 
     static final Key<Long> START_TIME_KEY = new Key<>("startTime", false);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,10 +38,12 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.BackendInformation;
 
+@Service
 public interface BackendInfoDAO {
 
     static final Key<String> BACKEND_NAME = new Key<>("name", true);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,10 +38,12 @@
 
 import java.util.Collection;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.HostInfo;
 
+@Service
 public interface HostInfoDAO extends Countable {
 
     static Key<String> hostNameKey = new Key<>("hostname", true);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,10 +38,12 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
 
+@Service
 public interface NetworkInterfaceInfoDAO {
 
     static Key<String> ifaceKey = new Key<>("interfaceName", true);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -40,10 +40,12 @@
 import java.util.List;
 import java.util.Map;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.VmInfo;
 
+@Service
 public interface VmInfoDAO extends Countable {
 
     static final Key<Integer> vmPidKey = new Key<>("vmPid", false);
--- a/common/core/src/main/java/com/redhat/thermostat/test/StubBundleContext.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/test/StubBundleContext.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -50,14 +50,21 @@
 import org.osgi.framework.BundleListener;
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.NotImplementedException;
-import com.redhat.thermostat.test.StubBundleContext.ServiceInformation;
 
+/**
+ * An implementation of BundleContext that's useful for writing unit tests.
+ * <p>
+ * WARNING: if you static mock {@link FrameworkUtil#createFilter(String)}, you
+ * are going to have a bad time.
+ */
 public class StubBundleContext implements BundleContext {
 
     static class ServiceInformation {
@@ -180,17 +187,28 @@
     public ServiceRegistration registerService(String className, Object service, Dictionary properties) {
         ServiceInformation info = new ServiceInformation(className, service, properties);
         registeredServices.add(info);
+
+        notifyServiceChange(new StubServiceReference(info), true);
+
         return new StubServiceRegistration(this, info);
     }
 
     @Override
     public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
-        Filter toMatch = createFilter(filter);
         List<ServiceReference> toReturn = new ArrayList<>();
-        for (ServiceInformation info : registeredServices) {
-            // how does filter matching in OSGI work again?
-            if (info.serviceInterface.equals(clazz) && toMatch.match(info.properties)) {
-                toReturn.add(new StubServiceReference(info));
+
+        if (filter == null) {
+            for (ServiceInformation info : registeredServices) {
+                if (info.serviceInterface.equals(clazz)) {
+                    toReturn.add(new StubServiceReference(info));
+                }
+            }
+        } else {
+            Filter toMatch = createFilter(filter);
+            for (ServiceInformation info : registeredServices) {
+                if (info.serviceInterface.equals(clazz) && toMatch.match(info.properties)) {
+                    toReturn.add(new StubServiceReference(info));
+                }
             }
         }
         return toReturn.toArray(new ServiceReference[0]);
@@ -203,7 +221,13 @@
 
     @Override
     public ServiceReference getServiceReference(String clazz) {
-        throw new NotImplementedException();
+        ServiceReference result = null;
+        for (ServiceInformation info : registeredServices) {
+            if (info.serviceInterface.equals(clazz)) {
+                result = new StubServiceReference(info);
+            }
+        }
+        return result;
     }
 
     @Override
@@ -247,7 +271,11 @@
 
     @Override
     public Filter createFilter(String filter) throws InvalidSyntaxException {
-        return new StubFilter(filter);
+        // FIXME this will break service trackers if FrameworkUtil is mocked
+        // the following call will return null if FrameworkUtil is mocked.
+        // that's a problem because this is meant to be used (mostly) in test
+        // environments and that's where FrameworkUtil is likely to be mocked.
+        return FrameworkUtil.createFilter(filter);
     }
 
     @Override
@@ -285,7 +313,17 @@
             throw new IllegalStateException("service not registered");
         }
         registeredServices.remove(info);
+        notifyServiceChange(new StubServiceReference(info), false);
+    }
 
+    private void notifyServiceChange(ServiceReference serviceReference, boolean registered) {
+        int eventType = registered ? ServiceEvent.REGISTERED : ServiceEvent.UNREGISTERING;
+        ServiceEvent event = new ServiceEvent(eventType, serviceReference);
+        for (ListenerSpec l : registeredListeners) {
+            if (l.filter.match(serviceReference)) {
+                l.listener.serviceChanged(event);
+            }
+        }
     }
 
     public int getExportedServiceCount(ServiceRegistration registration) {
--- a/common/core/src/main/java/com/redhat/thermostat/test/StubFilter.java	Tue Jan 15 23:00:52 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +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.test;
-
-import java.util.Dictionary;
-import java.util.Map;
-
-import org.osgi.framework.Filter;
-import org.osgi.framework.ServiceReference;
-
-import com.redhat.thermostat.common.NotImplementedException;
-
-public class StubFilter implements Filter {
-
-    private final String filter;
-
-    public StubFilter(String filter) {
-        this.filter = filter;
-    }
-
-    @Override
-    public boolean match(ServiceReference reference) {
-        if (filter == null) {
-            return true;
-        }
-        throw new NotImplementedException();
-    }
-
-    @Override
-    public boolean match(Dictionary dictionary) {
-        if (filter == null) {
-            return true;
-        }
-        throw new NotImplementedException();
-    }
-
-    @Override
-    public boolean matchCase(Dictionary dictionary) {
-        if (filter == null) {
-            return true;
-        }
-        throw new NotImplementedException();
-    }
-
-    @Override
-    public boolean matches(Map map) {
-        if (filter == null) {
-            return true;
-        }
-        throw new NotImplementedException();
-    }
-
-}
--- a/common/core/src/main/java/com/redhat/thermostat/test/StubServiceReference.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/test/StubServiceReference.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -36,6 +36,11 @@
 
 package com.redhat.thermostat.test;
 
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
@@ -57,12 +62,22 @@
             return new String[] { information.serviceInterface };
         }
 
-        throw new NotImplementedException();
+        return information.properties.get(key);
     }
 
     @Override
     public String[] getPropertyKeys() {
-        throw new NotImplementedException();
+        if (information.properties == null) {
+            return new String[] { Constants.OBJECTCLASS };
+        } else {
+            Dictionary props = information.properties;
+            List<String> toReturn = new ArrayList<>(props.size());
+            Enumeration keyEnumeration = props.keys();
+            while (keyEnumeration.hasMoreElements()) {
+                toReturn.add((String) keyEnumeration.nextElement());
+            }
+            return toReturn.toArray(new String[0]);
+        }
     }
 
     @Override
--- a/common/test/src/main/java/com/redhat/thermostat/test/locale/AbstractLocaleResourcesTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/common/test/src/main/java/com/redhat/thermostat/test/locale/AbstractLocaleResourcesTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,3 +1,39 @@
+/*
+ * 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.test.locale;
 
 import java.io.IOException;
@@ -31,4 +67,4 @@
     
     protected abstract String getResourceBundle();
 
-}
\ No newline at end of file
+}
--- a/distribution/config/commands/vm-stat.properties	Tue Jan 15 23:00:52 2013 +0100
+++ b/distribution/config/commands/vm-stat.properties	Tue Jan 15 23:04:57 2013 +0100
@@ -1,6 +1,8 @@
 bundles = thermostat-client-cli-${project.version}.jar, \
           thermostat-vm-cpu-common-${project.version}.jar, \
+          thermostat-vm-cpu-client-cli-${project.version}.jar, \
           thermostat-vm-memory-common-${project.version}.jar, \
+          thermostat-vm-memory-client-cli-${project.version}.jar, \
           thermostat-storage-mongodb-${project.version}.jar, \
           thermostat-web-common-${project.version}.jar, \
           thermostat-web-client-${project.version}.jar, \
--- a/distribution/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/distribution/pom.xml	Tue Jan 15 23:04:57 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>
@@ -412,6 +407,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-cpu-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-vm-cpu-agent</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -447,6 +447,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-memory-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-vm-memory-agent</artifactId>
       <version>${project.version}</version>
     </dependency>
--- a/host-cpu/client-core/src/main/java/com/redhat/thermostat/host/cpu/client/core/HostCpuViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/host-cpu/client-core/src/main/java/com/redhat/thermostat/host/cpu/client/core/HostCpuViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.host.cpu.client.core;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@Service
 public interface HostCpuViewProvider extends ViewProvider {
 
     @Override
--- a/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/CpuStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/CpuStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,12 +38,14 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.Countable;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.CpuStat;
 
+@Service
 public interface CpuStatDAO extends Countable {
 
     static Key<List<Double>> cpuLoadKey = new Key<>("perProcessorUsage", false);
--- a/host-memory/client-core/src/main/java/com/redhat/thermostat/host/memory/client/core/HostMemoryViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/host-memory/client-core/src/main/java/com/redhat/thermostat/host/memory/client/core/HostMemoryViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.host.memory.client.core;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@Service
 public interface HostMemoryViewProvider extends ViewProvider {
 
     @Override
--- a/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/MemoryStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/MemoryStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,12 +38,14 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.Countable;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.MemoryStat;
 
+@Service
 public interface MemoryStatDAO extends Countable {
 
     static Key<Long> memoryTotalKey = new Key<>("total", false);
--- a/host-overview/client-core/src/main/java/com/redhat/thermostat/host/overview/client/core/HostOverviewViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/host-overview/client-core/src/main/java/com/redhat/thermostat/host/overview/client/core/HostOverviewViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.host.overview.client.core;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@Service
 public interface HostOverviewViewProvider extends ViewProvider {
 
     @Override
--- a/keyring/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/keyring/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -115,6 +115,12 @@
       <artifactId>org.osgi.compendium</artifactId>
       <scope>provided</scope>
     </dependency>
-    
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 </project>
--- a/keyring/src/main/java/com/redhat/thermostat/utils/keyring/Keyring.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/keyring/src/main/java/com/redhat/thermostat/utils/keyring/Keyring.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,14 @@
 
 package com.redhat.thermostat.utils.keyring;
 
+import com.redhat.thermostat.annotations.Service;
+
 /**
  * Manages sensitive data, like {@link Credentials}, securely.
+ * <p>
+ * An instance of this class can be obtained from OSGi as a service.
  */
+@Service
 public interface Keyring {
 
     /**
--- a/launcher/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/pom.xml	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,71 @@
+/*
+ * 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.annotations.Service;
+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.
+ */
+@Service
+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/Launcher.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/Launcher.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,12 +38,14 @@
 
 import java.util.Collection;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.tools.ApplicationState;
 
 /**
  * Launcher is the main entry point for all Thermostat commands.
  */
+@Service
 public interface Launcher {
 
     /**
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 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	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/pom.xml	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory	Tue Jan 15 23:04:57 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	Tue Jan 15 23:00:52 2013 +0100
+++ b/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -117,10 +117,10 @@
   </repositories>
 
   <modules>
+    <module>annotations</module>
     <module>distribution</module>
     <module>main</module>
     <module>launcher</module>
-    <module>bundles</module>
     <module>common</module>
     <module>agent</module>
     <module>client</module>
--- a/storage/core/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/storage/core/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -84,6 +84,12 @@
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.framework</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-annotations</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 
 </project>
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Tue Jan 15 23:04:57 2013 +0100
@@ -39,6 +39,7 @@
 import java.io.InputStream;
 import java.util.UUID;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.model.Pojo;
 
 /**
@@ -46,6 +47,7 @@
  * Implementations may use memory, a file, some database or even a network
  * server as the backing store.
  */
+@Service
 public interface Storage {
 
     void setAgentId(UUID id);
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/StorageProviderUtil.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/StorageProviderUtil.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,3 +1,39 @@
+/*
+ * 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.storage.core;
 
 import org.osgi.framework.Bundle;
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/Activator.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/Activator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,3 +1,39 @@
+/*
+ * 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.storage.mongodb.internal;
 
 import org.osgi.framework.BundleActivator;
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Tue Jan 15 23:04:57 2013 +0100
@@ -52,14 +52,14 @@
 import sun.jvmstat.monitor.event.HostListener;
 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
 
-import com.redhat.thermostat.agent.JvmStatusListener;
-import com.redhat.thermostat.agent.JvmStatusNotifier;
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListener.Status;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.utils.ProcDataSource;
 
-public class JvmStatHostListener implements HostListener, JvmStatusNotifier {
+public class JvmStatHostListener implements HostListener {
 
     private static final Logger logger = LoggingUtils.getLogger(JvmStatHostListener.class);
 
@@ -67,10 +67,11 @@
 
     private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
     
-    private Set<JvmStatusListener> statusListeners = new CopyOnWriteArraySet<JvmStatusListener>();
+    private VmStatusChangeNotifier notifier;
 
-    JvmStatHostListener(VmInfoDAO vmInfoDAO) {
+    JvmStatHostListener(VmInfoDAO vmInfoDAO, VmStatusChangeNotifier notifier) {
         this.vmInfoDAO = vmInfoDAO;
+        this.notifier = notifier;
     }
 
     @Override
@@ -121,9 +122,7 @@
                 logger.log(Level.WARNING, "error getting vm info for " + vmId, me);
             }
 
-            for (JvmStatusListener statusListener : statusListeners) {
-                statusListener.jvmStarted(vmId);
-            }
+            notifier.notifyVmStatusChange(Status.VM_STARTED, vmId);
 
             monitoredVms.put(vmId, vm);
         }
@@ -149,9 +148,9 @@
         VmIdentifier resolvedVmID = host.getHostIdentifier().resolve(new VmIdentifier(vmId.toString()));
         if (resolvedVmID != null) {
             long stopTime = System.currentTimeMillis();
-            for (JvmStatusListener statusListener : statusListeners) {
-                statusListener.jvmStopped(vmId);
-            }
+
+            notifier.notifyVmStatusChange(Status.VM_STOPPED, vmId);
+
             vmInfoDAO.putVmStoppedTime(vmId, stopTime);
 
             MonitoredVm vm = monitoredVms.remove(vmId);
@@ -159,16 +158,6 @@
         }
     }
 
-    @Override
-    public void addJvmStatusListener(JvmStatusListener listener) {
-        statusListeners.add(listener);
-    }
-
-    @Override
-    public void removeJvmStatusListener(JvmStatusListener listener) {
-        statusListeners.remove(listener);
-    }
-    
     /*
      * For testing purposes only.
      */
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Tue Jan 15 23:04:57 2013 +0100
@@ -48,8 +48,7 @@
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
 
-import com.redhat.thermostat.agent.JvmStatusListener;
-import com.redhat.thermostat.agent.JvmStatusNotifier;
+import com.redhat.thermostat.agent.VmStatusListener;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
@@ -59,14 +58,16 @@
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
 import com.redhat.thermostat.utils.ProcDataSource;
 
-public class SystemBackend extends Backend implements JvmStatusNotifier, JvmStatusListener {
+public class SystemBackend extends Backend {
 
     private static final Logger logger = LoggingUtils.getLogger(SystemBackend.class);
 
     private HostInfoDAO hostInfos;
     private NetworkInterfaceInfoDAO networkInterfaces;
 
-    private final Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<Integer>();
+    private final VmStatusChangeNotifier notifier;
+
+    private final Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<>();
 
     private long procCheckInterval = 1000; // TODO make this configurable.
 
@@ -78,8 +79,10 @@
 
     private final HostInfoBuilder hostInfoBuilder;
 
-    public SystemBackend() {
+
+    public SystemBackend(VmStatusChangeNotifier notifier) {
         super(new BackendID("System Backend", SystemBackend.class.getName()));
+        this.notifier = notifier;
 
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers basic information from the system");
@@ -93,7 +96,7 @@
     protected void setDAOFactoryAction() {
         hostInfos = df.getHostInfoDAO();
         networkInterfaces = df.getNetworkInterfaceInfoDAO();
-        hostListener = new JvmStatHostListener(df.getVmInfoDAO());
+        hostListener = new JvmStatHostListener(df.getVmInfoDAO(), notifier);
     }
 
     @Override
@@ -105,8 +108,6 @@
             throw new IllegalStateException("Cannot activate backend without DAOFactory.");
         }
 
-        addJvmStatusListener(this);
-
         if (!getObserveNewJvm()) {
             logger.fine("not monitoring new vms");
         }
@@ -144,8 +145,6 @@
         timer.cancel();
         timer = null;
 
-        removeJvmStatusListener(this);
-
         try {
             host.removeHostListener(hostListener);
         } catch (MonitorException me) {
@@ -173,28 +172,6 @@
     }
 
     @Override
-    public void addJvmStatusListener(JvmStatusListener listener) {
-        hostListener.addJvmStatusListener(listener);
-    }
-
-    @Override
-    public void removeJvmStatusListener(JvmStatusListener listener) {
-        hostListener.removeJvmStatusListener(listener);
-    }
-
-    @Override
-    public void jvmStarted(int vmId) {
-        if (getObserveNewJvm()) {
-            pidsToMonitor.add(vmId);
-        }
-    }
-
-    @Override
-    public void jvmStopped(int vmId) {
-        pidsToMonitor.remove(vmId);
-    }
-
-    @Override
     public int getOrderValue() {
         return ORDER_DEFAULT_GROUP;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/VmStatusChangeNotifier.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.backend.system;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+
+/**
+ * Notifies any and all {@link VmStatusListener} registered as OSGi Services
+ * about VM status changes: {@link VmStatusListener.Status#VM_STARTED} and
+ * {@link VmStatusListener.Status#VM_STOPPED}.
+ * <p>
+ * Any listeners registered after a {@link VmStatusListener.Status#VM_STARTED}
+ * was delivered receive a {@link VmStatusListener.Status#VM_ACTIVE} event
+ * instead as an indication that a VM was started at some unknown point
+ * previously.
+ */
+public class VmStatusChangeNotifier {
+
+    private final Object listenerLock = new Object();
+    private final Set<Integer> activePids;
+    private final Map<VmStatusListener, Set<Integer>> listeners = new HashMap<>();
+
+    private final ServiceTracker tracker;
+
+    public VmStatusChangeNotifier(BundleContext bundleContext) {
+        this.activePids = new TreeSet<>();
+
+        tracker = new ServiceTracker(bundleContext, VmStatusListener.class, null) {
+            @Override
+            public VmStatusListener addingService(ServiceReference reference) {
+                VmStatusListener listener = (VmStatusListener) super.addingService(reference);
+
+                synchronized (listenerLock) {
+                    Set<Integer> notifiedAbout = new TreeSet<>();
+                    for (Integer pid : activePids) {
+                        listener.vmStatusChanged(Status.VM_ACTIVE, pid);
+                        notifiedAbout.add(pid);
+                    }
+
+                    listeners.put(listener, notifiedAbout);
+                }
+
+                return listener;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference,
+                    Object service) {
+                VmStatusListener listener = (VmStatusListener) service;
+                listeners.remove(listener);
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+    public void start() {
+        tracker.open();
+    }
+
+    public void stop() {
+        tracker.close();
+    }
+
+    /**
+     * Notify all registered listeners about a Vm status change.
+     *
+     * @param newStatus either {@link VmStatusListener.Status#VM_STARTED} or
+     * {@link VmStatusListener.Status#VM_STOPPED}
+     * @param pid
+     */
+    public void notifyVmStatusChange(VmStatusListener.Status newStatus, int pid) {
+        if (newStatus == Status.VM_ACTIVE) {
+            throw new IllegalArgumentException("Dont pass in " + Status.VM_ACTIVE + ", that will be handled automatically");
+        }
+
+        synchronized (listenerLock) {
+            for (Entry<VmStatusListener, Set<Integer>> entry : listeners.entrySet()) {
+                entry.getKey().vmStatusChanged(newStatus, pid);
+                entry.getValue().add(pid);
+            }
+
+            if (newStatus == Status.VM_STARTED) {
+                activePids.add(pid);
+            } else {
+                activePids.remove(pid);
+            }
+        }
+    }
+
+}
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/osgi/SystemBackendActivator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -44,18 +44,24 @@
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.backend.system.SystemBackend;
+import com.redhat.thermostat.backend.system.VmStatusChangeNotifier;
 
 @SuppressWarnings("rawtypes")
 public class SystemBackendActivator implements BundleActivator {
 
     private ServiceTracker tracker;
     private SystemBackend backend;
+
+    private VmStatusChangeNotifier notifier;
     
     @SuppressWarnings("unchecked")
     @Override
     public void start(BundleContext context) throws Exception {
         
-        backend = new SystemBackend();
+        notifier = new VmStatusChangeNotifier(context);
+        notifier.start();
+
+        backend = new SystemBackend(notifier);
         
         tracker = new ServiceTracker(context, BackendService.class, null) {
             @Override
@@ -84,5 +90,6 @@
             backend.deactivate();
         }
         tracker.close();
+        notifier.stop();
     }
 }
--- a/system-backend/src/test/java/com/redhat/thermostat/backend/system/JvmStatHostListenerTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/JvmStatHostListenerTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -41,7 +41,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -62,6 +64,7 @@
 import sun.jvmstat.monitor.VmIdentifier;
 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
 
+import com.redhat.thermostat.agent.VmStatusListener.Status;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.VmInfo;
 
@@ -82,11 +85,14 @@
     private MonitoredVm monitoredVm2;
     private JvmStatDataExtractor extractor;
     private VmInfoDAO vmInfoDAO;
+    private VmStatusChangeNotifier notifier;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
         vmInfoDAO = mock(VmInfoDAO.class);
-        hostListener = new JvmStatHostListener(vmInfoDAO);
+        notifier = mock(VmStatusChangeNotifier.class);
+
+        hostListener = new JvmStatHostListener(vmInfoDAO, notifier);
         
         host = mock(MonitoredHost.class);
         HostIdentifier hostId = mock(HostIdentifier.class);
@@ -126,6 +132,8 @@
         assertTrue(hostListener.getMonitoredVms().containsKey(2));
         assertEquals(monitoredVm1, hostListener.getMonitoredVms().get(1));
         assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
+
+        verify(notifier, times(2)).notifyVmStatusChange(eq(Status.VM_STARTED), (isA(Integer.class)));
     }
     
     @Test
@@ -146,6 +154,9 @@
         assertFalse(hostListener.getMonitoredVms().containsKey(1));
         assertTrue(hostListener.getMonitoredVms().containsKey(2));
         assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
+
+        verify(notifier).notifyVmStatusChange(eq(Status.VM_STOPPED), (isA(Integer.class)));
+
     }
 
     private void startVMs() throws InterruptedException, MonitorException {
@@ -159,6 +170,8 @@
         when(event.getStarted()).thenReturn(started);
         when(event.getTerminated()).thenReturn(Collections.emptySet());
         hostListener.vmStatusChanged(event);
+
+        verify(notifier, times(2)).notifyVmStatusChange(eq(Status.VM_STARTED), isA(Integer.class));
     }
 
     @Test
--- a/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/SystemBackendTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -62,7 +62,10 @@
         when(df.getStorage()).thenReturn(s);
         when(df.getHostInfoDAO()).thenReturn(hDAO);
         when(df.getNetworkInterfaceInfoDAO()).thenReturn(nDAO);
-        b = new SystemBackend();
+
+        VmStatusChangeNotifier notifier = mock(VmStatusChangeNotifier.class);
+
+        b = new SystemBackend(notifier);
         b.setDAOFactory(df);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/system-backend/src/test/java/com/redhat/thermostat/backend/system/VmStatusChangeNotifierTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.backend.system;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class VmStatusChangeNotifierTest {
+
+    @Test
+    public void verifyWorksWithoutAnyListeners() {
+        final int VM_ID = 2;
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        VmStatusChangeNotifier notifier = new VmStatusChangeNotifier(bundleContext);
+        notifier.start();
+        notifier.notifyVmStatusChange(Status.VM_STARTED, VM_ID);
+
+        notifier.notifyVmStatusChange(Status.VM_STOPPED, VM_ID);
+    }
+
+    @Test
+    public void verifyAllListenersAreNotified() {
+        final int VM_ID = 2;
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        VmStatusListener listener = mock(VmStatusListener.class);
+        bundleContext.registerService(VmStatusListener.class, listener, null);
+
+        VmStatusChangeNotifier notifier = new VmStatusChangeNotifier(bundleContext);
+        notifier.start();
+        notifier.notifyVmStatusChange(Status.VM_STARTED, VM_ID);
+
+        verify(listener).vmStatusChanged(Status.VM_STARTED, VM_ID);
+
+        notifier.notifyVmStatusChange(Status.VM_STOPPED, VM_ID);
+
+        verify(listener).vmStatusChanged(Status.VM_STOPPED, VM_ID);
+    }
+
+    @Test
+    public void verifyListenersAddedAfterVmStartRecieveVmActiveEvent() {
+        final int VM_ID = 2;
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        VmStatusChangeNotifier notifier = new VmStatusChangeNotifier(bundleContext);
+        notifier.start();
+        notifier.notifyVmStatusChange(Status.VM_STARTED, VM_ID);
+
+        VmStatusListener listener = mock(VmStatusListener.class);
+        bundleContext.registerService(VmStatusListener.class, listener, null);
+
+        verify(listener).vmStatusChanged(Status.VM_ACTIVE, VM_ID);
+
+    }
+}
--- a/unix-process-handler/src/main/java/com/redhat/thermostat/service/process/UNIXProcessHandler.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/unix-process-handler/src/main/java/com/redhat/thermostat/service/process/UNIXProcessHandler.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.service.process;
 
+import com.redhat.thermostat.annotations.Service;
+
+@Service
 public interface UNIXProcessHandler {
     
     public static String ID = "com.redhat.thermostat.service.process.UNIXProcessHandler";
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,9 +36,10 @@
 
 package com.redhat.thermostat.vm.classstat.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
-
+@ExtensionPoint
 public interface VmClassStatViewProvider extends ViewProvider {
 
     @Override
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,11 +38,13 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.VmClassStat;
 
+@Service
 public interface VmClassStatDAO {
 
     static final Key<Long> loadedClassesKey = new Key<>("loadedClasses", false);
@@ -54,4 +56,4 @@
 
     public void putVmClassStat(VmClassStat stat);
 
-}
\ No newline at end of file
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+    <artifactId>thermostat-vm-cpu</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-vm-cpu-client-cli</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat VM CPU CLI Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Private-Package>
+                com.redhat.thermostat.vm.cpu.client.cli.internal
+            </Private-Package>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.vm.cpu.client.cli.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.vm.cpu.client.cli</Bundle-SymbolicName>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <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.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-cpu-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/main/java/com/redhat/thermostat/vm/cpu/client/cli/internal/Activator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+
+public class Activator implements BundleActivator {
+    
+    private ServiceTracker tracker;
+    private ServiceRegistration reg;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        tracker = new ServiceTracker(context, VmCpuStatDAO.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                VmCpuStatDAO vmCpuStatDAO = (VmCpuStatDAO) super.addingService(reference);
+                VmCpuStatPrintDelegate delegate = new VmCpuStatPrintDelegate(vmCpuStatDAO);
+                reg = context.registerService(VMStatPrintDelegate.class.getName(), delegate, null);
+                return vmCpuStatDAO;
+            }
+            
+            @Override
+            public void removedService(ServiceReference reference,
+                    Object service) {
+                reg.unregister();
+                super.removedService(reference, service);
+            }
+        };
+        
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/main/java/com/redhat/thermostat/vm/cpu/client/cli/internal/LocaleResources.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import com.redhat.thermostat.common.locale.Translate;
+
+public enum LocaleResources {
+
+    COLUMN_HEADER_CPU_PERCENT,
+    ;
+
+    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.cpu.client.cli.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/main/java/com/redhat/thermostat/vm/cpu/client/cli/internal/VmCpuStatPrintDelegate.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import java.text.DecimalFormat;
+import java.util.Arrays;
+import java.util.List;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+import com.redhat.thermostat.storage.model.VmCpuStat;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+
+public class VmCpuStatPrintDelegate implements VMStatPrintDelegate {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    
+    private static final String CPU_PERCENT = translator.localize(LocaleResources.COLUMN_HEADER_CPU_PERCENT);
+    
+    private VmCpuStatDAO cpuStatDAO;
+
+    public VmCpuStatPrintDelegate(VmCpuStatDAO cpuStatDAO) {
+        this.cpuStatDAO = cpuStatDAO;
+    }
+
+    @Override
+    public List<? extends TimeStampedPojo> getLatestStats(VmRef ref,
+            long timestamp) {
+        return cpuStatDAO.getLatestVmCpuStats(ref, timestamp);
+    }
+
+    @Override
+    public List<String> getHeaders(TimeStampedPojo stat) {
+        return Arrays.asList(CPU_PERCENT);
+    }
+
+    @Override
+    public List<String> getStatRow(TimeStampedPojo stat) {
+        VmCpuStat cpuStat = (VmCpuStat) stat;
+        DecimalFormat format = new DecimalFormat("#0.0");
+        String cpuLoad = cpuStat != null ? format.format(cpuStat.getCpuLoad()) : "";
+        return Arrays.asList(cpuLoad);
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER_CPU_GROUP;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/main/resources/com/redhat/thermostat/vm/cpu/client/cli/strings.properties	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,1 @@
+COLUMN_HEADER_CPU_PERCENT = %CPU
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/test/java/com/redhat/thermostat/vm/cpu/client/cli/internal/ActivatorTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.test.StubBundleContext;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyActivatorDoesNotRegisterServiceOnMissingDeps() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertEquals(0, context.getAllServices().size());
+        assertEquals(1, context.getServiceListeners().size());
+
+        activator.stop(context);
+    }
+
+    @Test
+    public void verifyActivatorRegistersServices() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        VmCpuStatDAO dao = mock(VmCpuStatDAO.class);
+
+        context.registerService(VmCpuStatDAO.class, dao, null);
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertTrue(context.isServiceRegistered(VMStatPrintDelegate.class.getName(), VmCpuStatPrintDelegate.class));
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+        assertEquals(1, context.getAllServices().size());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/test/java/com/redhat/thermostat/vm/cpu/client/cli/internal/LocaleResourcesTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import com.redhat.thermostat.test.locale.AbstractLocaleResourcesTest;
+
+public class LocaleResourcesTest extends AbstractLocaleResourcesTest<LocaleResources> {
+
+    @Override
+    protected Class<LocaleResources> getEnumClass() {
+        return LocaleResources.class;
+    }
+
+    @Override
+    protected String getResourceBundle() {
+        return LocaleResources.RESOURCE_BUNDLE;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-cpu/client-cli/src/test/java/com/redhat/thermostat/vm/cpu/client/cli/internal/VmCpuStatPrintDelegateTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.cpu.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+import com.redhat.thermostat.storage.model.VmCpuStat;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+
+public class VmCpuStatPrintDelegateTest {
+
+    private VmCpuStatDAO vmCpuStatDAO;
+    private VmCpuStatPrintDelegate delegate;
+    private VmRef vm;
+    private List<VmCpuStat> cpuStats;
+
+    @Before
+    public void setUp() {
+        setupDAOs();
+        delegate = new VmCpuStatPrintDelegate(vmCpuStatDAO);
+    }
+
+    @After
+    public void tearDown() {
+        vmCpuStatDAO = null;
+    }
+
+    private void setupDAOs() {
+        vmCpuStatDAO = mock(VmCpuStatDAO.class);
+        int vmId = 234;
+        HostRef host = new HostRef("123", "dummy");
+        vm = new VmRef(host, 234, "dummy");
+        VmCpuStat cpustat1 = new VmCpuStat(2, vmId, 65);
+        VmCpuStat cpustat2 = new VmCpuStat(3, vmId, 70);
+        cpuStats = Arrays.asList(cpustat1, cpustat2);
+        when(vmCpuStatDAO.getLatestVmCpuStats(vm, Long.MIN_VALUE)).thenReturn(cpuStats);
+    }
+    
+    @Test
+    public void testGetLatestStats() {
+        List<? extends TimeStampedPojo> stats = delegate.getLatestStats(vm, Long.MIN_VALUE);
+        assertEquals(cpuStats, stats);
+    }
+    
+    @Test
+    public void testGetHeaders() {
+        List<String> headers = delegate.getHeaders(cpuStats.get(0));
+        assertEquals(Arrays.asList("%CPU"), headers);
+    }
+
+    @Test
+    public void testGetStatRow() throws CommandException {
+        final List<String> row1 = Arrays.asList("65.0");
+        final List<String> row2 = Arrays.asList("70.0");
+        assertEquals(row1, delegate.getStatRow(cpuStats.get(0)));
+        assertEquals(row2, delegate.getStatRow(cpuStats.get(1)));
+    }
+
+}
--- a/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-cpu/client-core/src/main/java/com/redhat/thermostat/vm/cpu/client/core/VmCpuViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.cpu.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface VmCpuViewProvider extends ViewProvider {
 
     @Override
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,11 +38,13 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.VmCpuStat;
 
+@Service
 public interface VmCpuStatDAO {
 
     static final Key<Double> vmCpuLoadKey = new Key<>("cpuLoad", false);
--- a/vm-cpu/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-cpu/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -52,6 +52,7 @@
 
   <modules>
     <module>agent</module>
+    <module>client-cli</module>
     <module>client-core</module>
     <module>client-swing</module>
     <module>common</module>
--- a/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/Activator.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/Activator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -42,6 +42,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendService;
 import com.redhat.thermostat.common.MultipleServiceTracker;
@@ -61,13 +62,16 @@
                 BackendService.class,
                 VmGcStatDAO.class
         };
+
+        final VmStatusListenerRegistrar registerer = new VmStatusListenerRegistrar(context);
+
         tracker = new MultipleServiceTracker(context, deps, new Action() {
 
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmGcStatDAO vmGcStatDao = (VmGcStatDAO) services.get(VmGcStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmGcBackend(vmGcStatDao, version);
+                backend = new VmGcBackend(vmGcStatDao, version, registerer);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Tue Jan 15 23:04:57 2013 +0100
@@ -37,13 +37,20 @@
 package com.redhat.thermostat.vm.gc.agent.internal;
 
 import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
+import sun.jvmstat.monitor.event.VmListener;
 
+import com.redhat.thermostat.agent.VmStatusListener;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
@@ -51,28 +58,29 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
 
-public class VmGcBackend extends Backend {
+public class VmGcBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmGcBackend.class);
 
-    private VmGcStatDAO vmGcStats;
-    private HostIdentifier hostId;
+    private final VmGcStatDAO vmGcStats;
+    private final VmStatusListenerRegistrar registerer;
+
+    private final Map<Integer, VmAndListener> registeredListeners = new HashMap<>();
     private MonitoredHost host;
-    private VmGcHostListener hostListener;
     private boolean started;
 
-    public VmGcBackend(VmGcStatDAO vmGcStatDAO, Version version) {
+    public VmGcBackend(VmGcStatDAO vmGcStatDAO, Version version, VmStatusListenerRegistrar registerer) {
         super(new BackendID("VM GC Backend", VmGcBackend.class.getName()));
         this.vmGcStats = vmGcStatDAO;
+        this.registerer = registerer;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers garbage collection statistics about a JVM");
         setConfigurationValue(BackendsProperties.VERSION.name(), version.getVersionNumber());
         
         try {
-            hostId = new HostIdentifier((String) null);
+            HostIdentifier hostId = new HostIdentifier((String) null);
             host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener = new VmGcHostListener(vmGcStats, attachToNewProcessByDefault());
         } catch (MonitorException me) {
             LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
         } catch (URISyntaxException use) {
@@ -80,28 +88,22 @@
         }
     }
 
+    // Methods from Backend
+
     @Override
     public boolean activate() {
-        if (!started && host != null) {
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "problems with connecting jvmstat to local machine", me);
-            }
+        if (!started) {
+            registerer.register(this);
+            started = true;
         }
         return started;
     }
 
     @Override
     public boolean deactivate() {
-        if (started && host != null) {
-            try {
-                host.removeHostListener(hostListener);
-                started = false;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.INFO, "something went wrong in jvmstat's listening to this host");
-            }
+        if (started) {
+            registerer.unregister(this);
+            started = false;
         }
         return !started;
     }
@@ -126,11 +128,76 @@
         return ORDER_MEMORY_GROUP + 20;
     }
 
+    // Methods from VmStatusListener
+
+    @Override
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            vmStarted(pid);
+            break;
+        case VM_STOPPED:
+            vmStopped(pid);
+            break;
+        }
+    }
+
+    private void vmStarted(int pid) {
+        if (attachToNewProcessByDefault()) {
+            try {
+                MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(new VmIdentifier(String.valueOf(pid))));
+                if (vm != null) {
+                    VmGcVmListener listener = new VmGcVmListener(vmGcStats, pid);
+                    vm.addVmListener(listener);
+                    registeredListeners.put(pid, new VmAndListener(vm, listener));
+                    LOGGER.finer("Attached VmListener for VM: " + pid);
+                } else {
+                    LOGGER.warning("could not connect to vm " + pid);
+                }
+            } catch (MonitorException me) {
+                LOGGER.log(Level.WARNING, "could not connect to vm " + pid, me);
+            } catch (URISyntaxException e) {
+                throw new AssertionError("The URI for the monitored vm must be valid, but it is not.");
+            }
+        }
+    }
+
+    private void vmStopped(int pid) {
+        VmAndListener tuple = registeredListeners.remove(pid);
+        if (tuple == null) {
+            LOGGER.warning("received vm stopped for an unknown VM");
+            return;
+        }
+
+        MonitoredVm vm = tuple.vm;
+        VmListener listener = tuple.listener;
+        try {
+            if (listener != null) {
+                vm.removeVmListener(listener);
+            }
+        } catch (MonitorException e) {
+            LOGGER.log(Level.WARNING, "can't remove vm listener", e);
+        }
+        vm.detach();
+    }
+
     /*
      * For testing purposes only.
      */
     void setHost(MonitoredHost host) {
         this.host = host;
     }
-    
+
+    private static class VmAndListener {
+        private MonitoredVm vm;
+        private VmListener listener;
+
+        public VmAndListener(MonitoredVm vm, VmListener listener) {
+            this.vm = vm;
+            this.listener = listener;
+        }
+
+    }
 }
--- a/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcHostListener.java	Tue Jan 15 23:00:52 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <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.vm.gc.agent.internal;
-
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.HostEvent;
-import sun.jvmstat.monitor.event.HostListener;
-import sun.jvmstat.monitor.event.VmListener;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
-
-public class VmGcHostListener implements HostListener {
-
-    private static final Logger logger = LoggingUtils.getLogger(VmGcHostListener.class);
-
-    private boolean attachNew;
-
-    private final VmGcStatDAO vmGcStatDAO;
-
-    private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
-    private Map<MonitoredVm, VmGcVmListener> registeredListeners  = new ConcurrentHashMap<>();
-    
-    VmGcHostListener(VmGcStatDAO vmGcStatDAO, boolean attachNew) {
-        this.vmGcStatDAO = vmGcStatDAO;
-        this.attachNew = attachNew;        
-    }
-
-    void removeAllListeners() {
-        for (MonitoredVm vm : monitoredVms.values()) {
-            VmListener listener = registeredListeners.get(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-        }
-    }
-    
-    @Override
-    public void disconnected(HostEvent event) {
-        logger.warning("Disconnected from host");
-    }
-
-    @SuppressWarnings("unchecked") // Unchecked casts to (Set<Integer>).
-    @Override
-    public void vmStatusChanged(VmStatusChangeEvent event) {
-        MonitoredHost host = event.getMonitoredHost();
-
-        for (Integer newVm : (Set<Integer>) event.getStarted()) {
-            try {
-                logger.fine("New vm: " + newVm);
-                sendNewVM(newVm, host);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for new vm" + newVm, e);
-            }
-        }
-
-        for (Integer stoppedVm : (Set<Integer>) event.getTerminated()) {
-            try {
-                logger.fine("stopped vm: " + stoppedVm);
-                sendStoppedVM(stoppedVm, host);
-            } catch (URISyntaxException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "error getting info for stopped vm" + stoppedVm, e);
-            }
-        }
-    }
-
-    private void sendNewVM(Integer vmId, MonitoredHost host)
-            throws MonitorException, URISyntaxException {
-        MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(
-                new VmIdentifier(vmId.toString())));
-        if (vm != null) {
-            if (attachNew) {
-                VmGcVmListener listener =  new VmGcVmListener(vmGcStatDAO, vmId);
-                vm.addVmListener(listener);
-                
-                registeredListeners.put(vm, listener);
-                logger.finer("Attached VmListener for VM: " + vmId);
-            } else {
-                logger.log(Level.FINE, "skipping new vm " + vmId);
-            }
-
-            monitoredVms.put(vmId, vm);
-        }
-    }
-
-    private void sendStoppedVM(Integer vmId, MonitoredHost host) throws URISyntaxException, MonitorException {
-        
-        VmIdentifier resolvedVmID = host.getHostIdentifier().resolve(new VmIdentifier(vmId.toString()));
-        if (resolvedVmID != null) {
-            MonitoredVm vm = monitoredVms.remove(vmId);
-            VmListener listener = registeredListeners.remove(vm);
-            try {
-                if (listener != null) {
-                    vm.removeVmListener(listener);
-                }
-            } catch (MonitorException e) {
-                logger.log(Level.WARNING, "can't remove vm listener", e);
-            }
-            vm.detach();
-        }
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<Integer, MonitoredVm> getMonitoredVms() {
-        return monitoredVms;
-    }
-    
-    /*
-     * For testing purposes only.
-     */
-    Map<MonitoredVm, VmGcVmListener> getRegisteredListeners() {
-        return registeredListeners;
-    }
-
-}
--- a/vm-gc/agent/src/test/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackendTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-gc/agent/src/test/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackendTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,7 +38,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -47,36 +47,59 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
+import sun.jvmstat.monitor.HostIdentifier;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.event.HostListener;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.VmIdentifier;
 
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.agent.VmStatusListener.Status;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
 
 public class VmGcBackendTest {
-    
+
     private VmGcBackend backend;
+    private VmStatusListenerRegistrar registerer;
+
     private MonitoredHost host;
+    private HostIdentifier hostIdentifier;
+    private MonitoredVm monitoredVm1;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
         VmGcStatDAO vmGcStatDao = mock(VmGcStatDAO.class);
-        
+
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
-        
-        backend = new VmGcBackend(vmGcStatDao, version);
-        
+
+        registerer = mock(VmStatusListenerRegistrar.class);
+
+        hostIdentifier = mock(HostIdentifier.class);
+        when(hostIdentifier.resolve(isA(VmIdentifier.class))).then(new Answer<VmIdentifier>() {
+            @Override
+            public VmIdentifier answer(InvocationOnMock invocation) throws Throwable {
+                return (VmIdentifier) invocation.getArguments()[0];
+            }
+        });
         host = mock(MonitoredHost.class);
+        when(host.getHostIdentifier()).thenReturn(hostIdentifier);
+
+        monitoredVm1 = mock(MonitoredVm.class);
+
+        backend = new VmGcBackend(vmGcStatDao, version, registerer);
+
         backend.setHost(host);
     }
 
     @Test
     public void testStart() throws MonitorException {
         backend.activate();
-        verify(host).addHostListener(any(HostListener.class));
+        verify(registerer).register(backend);
         assertTrue(backend.isActive());
     }
 
@@ -84,8 +107,31 @@
     public void testStop() throws MonitorException {
         backend.activate();
         backend.deactivate();
-        verify(host).removeHostListener(any(HostListener.class));
+        verify(registerer).unregister(backend);
         assertFalse(backend.isActive());
     }
-    
+
+    @Test
+    public void testNewVM() throws InterruptedException, MonitorException, URISyntaxException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
+        verify(monitoredVm1).addVmListener(isA(VmGcVmListener.class));
+    }
+
+    @Test
+    public void testStoppedVM() throws InterruptedException, MonitorException, URISyntaxException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(monitoredVm1).removeVmListener(isA(VmGcVmListener.class));
+    }
+
 }
--- a/vm-gc/agent/src/test/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcHostListenerTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <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.vm.gc.agent.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import sun.jvmstat.monitor.HostIdentifier;
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.MonitoredVm;
-import sun.jvmstat.monitor.VmIdentifier;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
-
-public class VmGcHostListenerTest {
-    
-    private VmGcHostListener hostListener;
-    private MonitoredHost host;
-    private MonitoredVm monitoredVm1;
-    private MonitoredVm monitoredVm2;
-
-    @Before
-    public void setup() throws MonitorException, URISyntaxException {
-        VmGcStatDAO vmGcStatDAO = mock(VmGcStatDAO.class);
-        hostListener = new VmGcHostListener(vmGcStatDAO, true);
-        
-        host = mock(MonitoredHost.class);
-        HostIdentifier hostId = mock(HostIdentifier.class);
-        monitoredVm1 = mock(MonitoredVm.class);
-        monitoredVm2 = mock(MonitoredVm.class);
-        VmIdentifier vmId1 = new VmIdentifier("1");
-        VmIdentifier vmId2 = new VmIdentifier("2");
-        when(host.getHostIdentifier()).thenReturn(hostId);
-        when(host.getMonitoredVm(eq(vmId1))).thenReturn(monitoredVm1);
-        when(host.getMonitoredVm(eq(vmId2))).thenReturn(monitoredVm2);
-        when(hostId.resolve(eq(vmId1))).thenReturn(vmId1);
-        when(hostId.resolve(eq(vmId2))).thenReturn(vmId2);
-    }
-    
-    @Test
-    public void testNewVM() throws InterruptedException, MonitorException {
-        startVMs();
-        
-        assertTrue(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm1, hostListener.getMonitoredVms().get(1));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-    
-    @Test
-    public void testStoppedVM() throws InterruptedException, MonitorException {
-        final Set<Integer> stopped = new HashSet<>();
-        stopped.add(1);
-        
-        startVMs();
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(Collections.emptySet());
-        when(event.getTerminated()).thenReturn(stopped);
-        hostListener.vmStatusChanged(event);
-        
-        // Ensure only 1 removed
-        assertFalse(hostListener.getMonitoredVms().containsKey(1));
-        assertTrue(hostListener.getMonitoredVms().containsKey(2));
-        assertEquals(monitoredVm2, hostListener.getMonitoredVms().get(2));
-        
-        assertFalse(hostListener.getRegisteredListeners().containsKey(monitoredVm1));
-        assertTrue(hostListener.getRegisteredListeners().containsKey(monitoredVm2));
-    }
-
-    private void startVMs() throws InterruptedException, MonitorException {
-        final Set<Integer> started = new HashSet<>();
-        started.add(1);
-        started.add(2);
-
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getMonitoredHost()).thenReturn(host);
-        when(event.getStarted()).thenReturn(started);
-        when(event.getTerminated()).thenReturn(Collections.emptySet());
-        hostListener.vmStatusChanged(event);
-    }
-}
--- a/vm-gc/client-core/src/main/java/com/redhat/thermostat/vm/gc/client/core/VmGcViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-gc/client-core/src/main/java/com/redhat/thermostat/vm/gc/client/core/VmGcViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.gc.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface VmGcViewProvider extends ViewProvider {
 
     @Override
--- a/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/VmGcStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/VmGcStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,11 +38,13 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.VmGcStat;
 
+@Service
 public interface VmGcStatDAO {
 
     static final Key<String> collectorKey = new Key<>("collectorName", false);
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumpDetailsViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumpDetailsViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface HeapDumpDetailsViewProvider extends ViewProvider {
 
     @Override
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface HeapHistogramViewProvider extends ViewProvider {
 
     @Override
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface HeapViewProvider extends ViewProvider {
 
     @Override
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/ObjectDetailsViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/ObjectDetailsViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface ObjectDetailsViewProvider extends ViewProvider {
 
     @Override
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/ObjectRootsViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/ObjectRootsViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.heap.analysis.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface ObjectRootsViewProvider extends ViewProvider {
 
     @Override
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -41,11 +41,13 @@
 import java.io.InputStream;
 import java.util.Collection;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.HeapInfo;
 
+@Service
 public interface HeapDAO {
 
     static final Key<String> heapIdKey = new Key<String>("heapId", false);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>
+    <artifactId>thermostat-vm-memory</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-vm-memory-client-cli</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat VM Memory CLI Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Private-Package>
+                com.redhat.thermostat.vm.memory.client.cli.internal
+            </Private-Package>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.vm.memory.client.cli.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.vm.memory.client.cli</Bundle-SymbolicName>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <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.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-memory-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/main/java/com/redhat/thermostat/vm/memory/client/cli/internal/Activator.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
+
+public class Activator implements BundleActivator {
+    
+    private ServiceTracker tracker;
+    private ServiceRegistration reg;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        tracker = new ServiceTracker(context, VmMemoryStatDAO.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                VmMemoryStatDAO vmMemoryStatDAO = (VmMemoryStatDAO) super.addingService(reference);
+                VmMemoryStatPrintDelegate delegate = new VmMemoryStatPrintDelegate(vmMemoryStatDAO);
+                reg = context.registerService(VMStatPrintDelegate.class.getName(), delegate, null);
+                return vmMemoryStatDAO;
+            }
+            
+            @Override
+            public void removedService(ServiceReference reference,
+                    Object service) {
+                reg.unregister();
+                super.removedService(reference, service);
+            }
+        };
+        
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/main/java/com/redhat/thermostat/vm/memory/client/cli/internal/LocaleResources.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import com.redhat.thermostat.common.locale.Translate;
+
+public enum LocaleResources {
+
+    VALUE_AND_UNIT,
+
+    COLUMN_HEADER_MEMORY_PATTERN,
+    ;
+
+    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.memory.client.cli.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/main/java/com/redhat/thermostat/vm/memory/client/cli/internal/VmMemoryStatPrintDelegate.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.common.Size;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+import com.redhat.thermostat.storage.model.VmMemoryStat;
+import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
+
+public class VmMemoryStatPrintDelegate implements VMStatPrintDelegate {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    
+    private VmMemoryStatDAO memoryStatDAO;
+    
+    public VmMemoryStatPrintDelegate(VmMemoryStatDAO memoryStatDAO) {
+        this.memoryStatDAO = memoryStatDAO;
+    }
+
+    @Override
+    public List<? extends TimeStampedPojo> getLatestStats(VmRef ref, long timestamp) {
+        return memoryStatDAO.getLatestVmMemoryStats(ref, timestamp);
+    }
+
+    @Override
+    public List<String> getHeaders(TimeStampedPojo stat) {
+        return getSpacesNames(stat);
+    }
+    
+    @Override
+    public List<String> getStatRow(TimeStampedPojo stat) {
+        return getMemoryUsage(stat);
+    }
+
+    private List<String> getSpacesNames(TimeStampedPojo stat) {
+        List<String> spacesNames = new ArrayList<>();
+        VmMemoryStat memStat = (VmMemoryStat) stat;
+        for (VmMemoryStat.Generation gen : memStat.getGenerations()) {
+            for (VmMemoryStat.Space space : gen.getSpaces()) {
+                spacesNames.add(translator.localize(LocaleResources.COLUMN_HEADER_MEMORY_PATTERN, space.getName()));
+            }
+        }
+        return spacesNames;
+    }
+
+    private List<String> getMemoryUsage(TimeStampedPojo stat) {
+        List<String> memoryUsage = new ArrayList<>();
+        for (VmMemoryStat.Generation gen : ((VmMemoryStat) stat).getGenerations()) {
+            for (VmMemoryStat.Space space : gen.getSpaces()) {
+                memoryUsage.add(Size.bytes(space.getUsed()).toString());
+            }
+        }
+        return memoryUsage;
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER_MEMORY_GROUP;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/main/resources/com/redhat/thermostat/vm/memory/client/cli/strings.properties	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,3 @@
+VALUE_AND_UNIT = {0} {1}
+
+COLUMN_HEADER_MEMORY_PATTERN = MEM.{0}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/test/java/com/redhat/thermostat/vm/memory/client/cli/internal/ActivatorTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.test.StubBundleContext;
+import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyActivatorDoesNotRegisterServiceOnMissingDeps() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertEquals(0, context.getAllServices().size());
+        assertEquals(1, context.getServiceListeners().size());
+
+        activator.stop(context);
+    }
+
+    @Test
+    public void verifyActivatorRegistersServices() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        VmMemoryStatDAO dao = mock(VmMemoryStatDAO.class);
+
+        context.registerService(VmMemoryStatDAO.class, dao, null);
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertTrue(context.isServiceRegistered(VMStatPrintDelegate.class.getName(), VmMemoryStatPrintDelegate.class));
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+        assertEquals(1, context.getAllServices().size());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/test/java/com/redhat/thermostat/vm/memory/client/cli/internal/LocaleResourcesTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import com.redhat.thermostat.test.locale.AbstractLocaleResourcesTest;
+
+public class LocaleResourcesTest extends AbstractLocaleResourcesTest<LocaleResources> {
+
+    @Override
+    protected Class<LocaleResources> getEnumClass() {
+        return LocaleResources.class;
+    }
+
+    @Override
+    protected String getResourceBundle() {
+        return LocaleResources.RESOURCE_BUNDLE;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-cli/src/test/java/com/redhat/thermostat/vm/memory/client/cli/internal/VmMemoryStatPrintDelegateTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <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.vm.memory.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.cli.VMStatPrintDelegate;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+import com.redhat.thermostat.storage.model.VmMemoryStat;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
+import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
+
+public class VmMemoryStatPrintDelegateTest {
+
+    private VmMemoryStatDAO vmMemoryStatDAO;
+    private VMStatPrintDelegate delegate;
+    private VmRef vm;
+    private List<VmMemoryStat> memoryStats;
+
+    @Before
+    public void setUp() {
+        setupDAOs();
+        delegate = new VmMemoryStatPrintDelegate(vmMemoryStatDAO);
+    }
+
+    @After
+    public void tearDown() {
+        vmMemoryStatDAO = null;
+    }
+
+    private void setupDAOs() {
+        int vmId = 234;
+        HostRef host = new HostRef("123", "dummy");
+        vm = new VmRef(host, 234, "dummy");
+
+        VmMemoryStat.Space space1_1_1 = newSpace("space1", 123456, 12345, 1, 0);
+        VmMemoryStat.Space space1_1_2 = newSpace("space2", 123456, 12345, 1, 0);
+        VmMemoryStat.Space[] spaces1_1 = new VmMemoryStat.Space[] { space1_1_1, space1_1_2 };
+        VmMemoryStat.Generation gen1_1 = newGeneration("gen1", "col1", 123456, 12345, spaces1_1);
+
+        VmMemoryStat.Space space1_2_1 = newSpace("space3", 123456, 12345, 1, 0);
+        VmMemoryStat.Space space1_2_2 = newSpace("space4", 123456, 12345, 1, 0);
+        VmMemoryStat.Space[] spaces1_2 = new VmMemoryStat.Space[] { space1_2_1, space1_2_2 };
+        VmMemoryStat.Generation gen1_2 = newGeneration("gen2", "col1", 123456, 12345, spaces1_2);
+
+        VmMemoryStat.Generation[] gens1 = new VmMemoryStat.Generation[] { gen1_1, gen1_2 };
+
+        VmMemoryStat memStat1 = new VmMemoryStat(1, vmId, gens1);
+
+        VmMemoryStat.Space space2_1_1 = newSpace("space1", 123456, 12345, 2, 0);
+        VmMemoryStat.Space space2_1_2 = newSpace("space2", 123456, 12345, 2, 0);
+        VmMemoryStat.Space[] spaces2_1 = new VmMemoryStat.Space[] { space2_1_1, space2_1_2 };
+        VmMemoryStat.Generation gen2_1 = newGeneration("gen1", "col1", 123456, 12345, spaces2_1);
+
+        VmMemoryStat.Space space2_2_1 = newSpace("space3", 123456, 12345, 3, 0);
+        VmMemoryStat.Space space2_2_2 = newSpace("space4", 123456, 12345, 4, 0);
+        VmMemoryStat.Space[] spaces2_2 = new VmMemoryStat.Space[] { space2_2_1, space2_2_2 };
+        VmMemoryStat.Generation gen2_2 = newGeneration("gen2", "col1", 123456, 12345, spaces2_2);
+
+        VmMemoryStat.Generation[] gens2 = new VmMemoryStat.Generation[] { gen2_1, gen2_2 };
+
+        VmMemoryStat memStat2 = new VmMemoryStat(2, vmId, gens2);
+
+        VmMemoryStat.Space space3_1_1 = newSpace("space1", 123456, 12345, 4, 0);
+        VmMemoryStat.Space space3_1_2 = newSpace("space2", 123456, 12345, 5, 0);
+        VmMemoryStat.Space[] spaces3_1 = new VmMemoryStat.Space[] { space3_1_1, space3_1_2 };
+        VmMemoryStat.Generation gen3_1 = newGeneration("gen1", "col1", 123456, 12345, spaces3_1);
+
+        VmMemoryStat.Space space3_2_1 = newSpace("space3", 123456, 12345, 6, 0);
+        VmMemoryStat.Space space3_2_2 = newSpace("space4", 123456, 12345, 7, 0);
+        VmMemoryStat.Space[] spaces3_2 = new VmMemoryStat.Space[] { space3_2_1, space3_2_2 };
+        VmMemoryStat.Generation gen3_2 = newGeneration("gen2", "col1", 123456, 12345, spaces3_2);
+
+        VmMemoryStat.Generation[] gens3 = new VmMemoryStat.Generation[] { gen3_1, gen3_2 };
+
+        VmMemoryStat memStat3 = new VmMemoryStat(3, vmId, gens3);
+
+        vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
+        memoryStats = Arrays.asList(memStat1, memStat2, memStat3);
+        when(vmMemoryStatDAO.getLatestVmMemoryStats(vm, Long.MIN_VALUE))
+            .thenReturn(memoryStats);
+    }
+
+    private Space newSpace(String name, long maxCapacity, long capacity, long used, int index) {
+        VmMemoryStat.Space space = new VmMemoryStat.Space();
+        space.setName(name);
+        space.setMaxCapacity(maxCapacity);
+        space.setCapacity(capacity);
+        space.setUsed(used);
+        space.setIndex(index);
+        return space;
+    }
+
+    private Generation newGeneration(String name, String collector, long maxCapacity, long capacity, Space[] spaces) {
+        VmMemoryStat.Generation gen = new VmMemoryStat.Generation();
+        gen.setName(name);
+        gen.setCollector(collector);
+        gen.setMaxCapacity(capacity);
+        gen.setSpaces(spaces);
+        return gen;
+    }
+
+    @Test
+    public void testGetLatestStats() {
+        List<? extends TimeStampedPojo> stats = delegate.getLatestStats(vm, Long.MIN_VALUE);
+        assertEquals(memoryStats, stats);
+    }
+    
+    @Test
+    public void testGetHeaders() {
+        List<String> headers = delegate.getHeaders(memoryStats.get(0));
+        assertEquals(Arrays.asList("MEM.space1", "MEM.space2", "MEM.space3", "MEM.space4"), headers);
+    }
+
+    @Test
+    public void testGetStatRow() throws CommandException {
+        final List<String> row1 = Arrays.asList("1 B", "1 B", "1 B", "1 B");
+        final List<String> row2 = Arrays.asList("2 B", "2 B", "3 B", "4 B");
+        final List<String> row3 = Arrays.asList("4 B", "5 B", "6 B", "7 B");
+        assertEquals(row1, delegate.getStatRow(memoryStats.get(0)));
+        assertEquals(row2, delegate.getStatRow(memoryStats.get(1)));
+        assertEquals(row3, delegate.getStatRow(memoryStats.get(2)));
+    }
+
+}
--- a/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryStatsViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryStatsViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.memory.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface MemoryStatsViewProvider extends ViewProvider {
 
     @Override
--- a/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/VmMemoryStatDAO.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/VmMemoryStatDAO.java	Tue Jan 15 23:04:57 2013 +0100
@@ -38,12 +38,14 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.model.VmMemoryStat;
 import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
 
+@Service
 public interface VmMemoryStatDAO {
 
     static final Key<Generation[]> generationsKey = new Key<>("generations", false);
--- a/vm-memory/pom.xml	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-memory/pom.xml	Tue Jan 15 23:04:57 2013 +0100
@@ -52,6 +52,7 @@
 
   <modules>
     <module>agent</module>
+    <module>client-cli</module>
     <module>client-core</module>
     <module>client-swing</module>
     <module>common</module>
--- a/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/VmOverviewViewProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/vm-overview/client-core/src/main/java/com/redhat/thermostat/vm/overview/client/core/VmOverviewViewProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -36,8 +36,10 @@
 
 package com.redhat.thermostat.vm.overview.client.core;
 
+import com.redhat.thermostat.annotations.ExtensionPoint;
 import com.redhat.thermostat.client.core.views.ViewProvider;
 
+@ExtensionPoint
 public interface VmOverviewViewProvider extends ViewProvider {
 
     @Override
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorageProvider.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorageProvider.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,3 +1,39 @@
+/*
+ * 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.web.client.internal;
 
 import com.redhat.thermostat.storage.config.AuthenticationConfiguration;
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Tue Jan 15 23:00:52 2013 +0100
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Tue Jan 15 23:04:57 2013 +0100
@@ -1,17 +1,3 @@
-package com.redhat.thermostat.web.common;
-import static org.junit.Assert.assertEquals;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.storage.model.Pojo;
-
 /*
  * Copyright 2012 Red Hat, Inc.
  *
@@ -48,6 +34,20 @@
  * to do so, delete this exception statement from your version.
  */
 
+package com.redhat.thermostat.web.common;
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.model.Pojo;
+
 public class WebQueryTest {
 
     private static class TestObj implements Pojo {