changeset 928:279aeb153688

Merge
author Roman Kennke <rkennke@redhat.com>
date Tue, 22 Jan 2013 16:15:32 +0100
parents 8a6697503832 (current diff) 4f3b798807f3 (diff)
children ccac9bcabaa0
files client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ContextActionServiceProvider.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListener.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListenerTest.java vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListener.java vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListenerTest.java vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListener.java vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListenerTest.java
diffstat 52 files changed, 1642 insertions(+), 1222 deletions(-) [+]
line wrap: on
line diff
--- a/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/agent/core/src/main/java/com/redhat/thermostat/backend/Backend.java	Tue Jan 22 16:15:32 2013 +0100
@@ -181,7 +181,8 @@
      * This method is called by the framework when the {@link Backend} is
      * registered.
      *
-     * @return true on success, false if there was an error
+     * @return {@code true} if the backend was activated successfully or
+     * already active. {@code false} if there was an error
      */
     public abstract boolean activate();
 
@@ -196,7 +197,8 @@
      * This method is called by the framework when the {@link Backend} is
      * deregistered.
      *
-     * @return true on success
+     * @return {@code true} if the backend was successfully deactivated or
+     * already inactive. {@code false} if the backend is still active.
      */
     public abstract boolean deactivate();
 
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ContextAction.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ContextAction.java	Tue Jan 22 16:15:32 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -36,31 +36,32 @@
 
 package com.redhat.thermostat.client.osgi.service;
 
-import com.redhat.thermostat.annotations.ExtensionPoint;
-
 /**
- * Marker service for context menu actions.
- * <br /><br />
- * 
- * Each specific subclass defines the selected entry points for the context
- * menus.
- * <br /><br />
+ * Parent interface for all context-sensitive actions.
+ * <p>
+ * {@code ContextAction}s are executed once the user selects the appropriate UI
+ * elements in the view and triggers the registered action.
+ * <p>
+ * The name of the action (as returned by {@link #getName()}) is likely to be
+ * user-visible and should be localized.
+ *
+ * <h2>Implementation Notes</h2>
+ * <p>
+ * The following information is specific to the current release and may change
+ * in a future release.
+ * <p>
+ * The swing client uses {@code ContextAction}s to mostly implement menus. Some
+ * of these menus are shown when a user right-clicks on a widget, but some are
+ * associated with a window.
  * 
- * Context actions are executed once the user select the appropriate UI elements
- * in the main framework view and trigger the registered action.
- * <br /><br />
- * 
- * An empty {@link ContextAction} is instantiated within the framework at
- * startup, so services implementing specific actions interfaces should track
- * for a {@link ContextAction} service to be active in the framework before
- * adding themselves.
- * <br /><br />
- * 
- * <strong>Exported entry point</strong>: com.redhat.thermostat.client.osgi.service.ContextAction
+ * @see MenuAction
+ * @see VMContextAction
  */
-@ExtensionPoint
 public interface ContextAction {
     
+    /** A user-visible name for this action */
     String getName();
+
+    /** A user-visible description for this action */
     String getDescription();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/HostContextAction.java	Tue Jan 22 16:15:32 2013 +0100
@@ -0,0 +1,94 @@
+/*
+ * 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.client.osgi.service;
+
+import com.redhat.thermostat.annotations.ExtensionPoint;
+import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.common.dao.HostRef;
+
+/**
+ * {@code HostContextAction}s provide actions that are associated with hosts and
+ * can be invoked by users. The exact position and appearance of these
+ * {@code HostContextAction}s varies based on the implementation.
+ * <p>
+ * Plugins can register implementations of this interface as OSGi services to
+ * provide additional {@code HostContextAction}s.
+ * <p>
+ * <h2>Implementation Note</h2>
+ * <p>
+ * The following information is specific to the current release and may change
+ * in a future release.
+ * <p>
+ * The swing client uses instances of this interface to provide menu items for
+ * the Host/VM tree. The menu is shown when a user right clicks a host in the
+ * Host/VM tree. A menu item for every {@code HostContextAction} is added, if
+ * the {@link Filter} matches, to this menu. Selecting a menu item invokes the
+ * appropriate {@code HostContextAction}.
+ *
+ * @see VMContextAction
+ */
+@ExtensionPoint
+public interface HostContextAction extends ContextAction {
+
+    /**
+     * A user-visible name for this {@code HostContextAction}. This should be
+     * localized.
+     */
+    @Override
+    String getName();
+
+    /**
+     * A user-visible description for this {@code HostContextAction}. This
+     * should be localized.
+     */
+    @Override
+    String getDescription();
+
+    /**
+     * Invoked when the user selects this {@code HostContextAction}.
+     *
+     * @param reference the host on which this {@code HostContextAction} was
+     * invoked on.
+     */
+    void execute(HostRef reference);
+
+    /**
+     * The {@link Filter} returned by this method is used to select what VMs
+     * this {@code HostContextAction} is applicable to.
+     */
+    Filter<HostRef> getFilter();
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java	Tue Jan 22 16:15:32 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -38,11 +38,21 @@
 import com.redhat.thermostat.annotations.ExtensionPoint;
 
 /**
- * Allows plugins to register menu items.
+ * {@code MenuAction}s are used to create top-level menu items in the main
+ * window.
  * <p>
- * To register a menu item for for the menu "File" in thermostat client window,
- * register a service that implements this class with the property
- * "parentMenu" set to "File".
+ * Plugins can register menu items by creating classes that implement this
+ * interface and registering them as OSGi services. To register a menu item for
+ * for the menu "File" in thermostat client window, register a service that
+ * returns <code> {"File", getName()}</code> from {@link #getPath()}.
+ *
+ * <h2>Implementation Notes</h2>
+ * <p>
+ * The following information is specific to the current release and may change
+ * in a future release.
+ * <p>
+ * The swing client uses {@code MenuActions}s to implement top-level menus in
+ * the main window only.
  */
 @ExtensionPoint
 public interface MenuAction extends ContextAction {
@@ -51,13 +61,23 @@
         CHECK,
         RADIO,
         STANDARD
-    };
+    }
+
+    /** The user-visible text displayed as the menu item. */
+    @Override
+    public String getName();
+
+    /** A user-visible description of what this {@code MenuAction} does. */
+    @Override
+    public String getDescription();
 
     /** Invoked when the user selects this menu item */
     void execute();
 
+    /** The type of the menu (radio, check, standard) */
     Type getType();
 
     /** The path to the menu action. The last element must equal getName() */
     String[] getPath();
+
 }
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Tue Jan 22 16:15:32 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -41,12 +41,55 @@
 import com.redhat.thermostat.common.dao.VmRef;
 
 /**
- * A context action for VMs
+ * {@code VMContextAction}s provide actions that are associated with Java
+ * Virtual Machines and can be invoked by users. The exact position and
+ * appearance of these {@code VMContextAction}s varies based on the client
+ * implementation.
+ * <p>
+ * Plugins can register implementation of this interface as OSGi services to
+ * provide additional {@code VMContextAction}s.
+ * <p>
+ * <h2>Implementation Note</h2>
+ * <p>
+ * The following information is specific to the current release and may change
+ * in a future release.
+ * <p>
+ * The swing client uses instances of this class to provide menu items in the
+ * Host/VM tree. The menu is shown when a user right-clicks a VM in the Host/VM
+ * tree. A menu item for every {@link VMContextAction} is added, if the
+ * {@code Filter} matches, to this menu. Selecting a menu item invokes the
+ * corresponding {@code VMContextAction}.
+ *
+ * @see HostContextAction
  */
 @ExtensionPoint
 public interface VMContextAction extends ContextAction {
 
-    void execute(VmRef referece);
+    /**
+     * A user-visible name for this {@code VMContextAction}. Should be
+     * localized.
+     */
+    @Override
+    public String getName();
 
+    /**
+     * A user-visible description for {@code VMContextAction}. Should be
+     * localized.
+     */
+    @Override
+    public String getDescription();
+
+    /**
+     * Invoked when the user selects this {@code VMContextAction}.
+     *
+     * @param reference specifies the vm that this {@code VMContextAction} was
+     * invoked on.
+     */
+    void execute(VmRef reference);
+
+    /**
+     * The {@link Filter} returned by this method is used to select what VMs
+     * this {@code VMContextAction} is applicable to.
+     */
     Filter<VmRef> getFilter();
 }
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Tue Jan 22 16:15:32 2013 +0100
@@ -40,6 +40,7 @@
 import java.util.List;
 
 import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.common.dao.HostInfoDAO;
 import com.redhat.thermostat.common.dao.HostRef;
@@ -72,6 +73,10 @@
 
     void removeVmInformationService(InformationService<VmRef> vmInfoService);
 
+    Collection<HostContextAction> getHostContextActions();
+    void addHostContextAction(HostContextAction action);
+    void removeHostContextAction(HostContextAction action);
+
     Collection<VMContextAction> getVMContextActions();
     void addVMContextAction(VMContextAction service);
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/MenuHelper.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/MenuHelper.java	Tue Jan 22 16:15:32 2013 +0100
@@ -215,8 +215,6 @@
     private static final class Menu {
         private Object swingDelegate;
 
-        public Menu() { /* no op */}
-
         public Menu(JMenuItem actual) {
             this.swingDelegate = actual;
         }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/GUIClientCommand.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/GUIClientCommand.java	Tue Jan 22 16:15:32 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -36,36 +36,22 @@
 
 package com.redhat.thermostat.client.swing.internal;
 
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-import com.redhat.thermostat.client.osgi.service.ContextAction;
-import com.redhat.thermostat.client.swing.internal.osgi.ContextActionServiceProvider;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.SimpleCommand;
 
 public class GUIClientCommand extends SimpleCommand {
 
-    private BundleContext context;
     private Main clientMain;
 
     public GUIClientCommand(Main clientMain) {
-        this(clientMain, FrameworkUtil.getBundle(GUIClientCommand.class).getBundleContext());
-    }
-    
-    GUIClientCommand(Main clientMain, BundleContext context) {
-        this.context = context;
         this.clientMain = clientMain;
     }
-    
+
     @Override
     public void run(CommandContext ctx) throws CommandException {
-        context.registerService(ContextAction.class.getName(), new ContextActionServiceProvider(), null);
-        
         // this blocks, everything else needs to be done before
         clientMain.run();
-
     }
 
     @Override
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Tue Jan 22 16:15:32 2013 +0100
@@ -43,9 +43,10 @@
 
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
-import com.redhat.thermostat.client.osgi.service.VMContextAction;
+import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.HostsVMsLoader;
 import com.redhat.thermostat.common.dao.HostRef;
@@ -64,8 +65,8 @@
         SWITCH_HISTORY_MODE,
         SHOW_ABOUT_DIALOG,
         SHUTDOWN,
-        SHOW_VM_CONTEXT_MENU,
-        VM_CONTEXT_ACTION,
+        SHOW_HOST_VM_CONTEXT_MENU,
+        HOST_VM_CONTEXT_ACTION,
     }
 
     void addActionListener(ActionListener<Action> capture);
@@ -96,11 +97,23 @@
 
     /**
      * Removes a menu item to the window. Assumes the menu path is valid (has a
-     * non-zero length) and doesn't collide with existing menus.
+     * non-zero length) and the menu already exists.
      */
     void removeMenu(MenuAction action);
 
-    void showVMContextActions(List<VMContextAction> actions, MouseEvent e);
+    /**
+     * Shows a popup context menu created from the list of supplied context
+     * actions. When an item in the popup menu is selected, an
+     * {@link ActionEvent} is fired with the id
+     * {@link Action#HOST_VM_CONTEXT_ACTION} and the user-selected
+     * {@link ContextAction} as the payload.
+     *
+     * @param actions the {@link ContextAction}s available to the user.
+     * Normally classes implementing sub-interfaces of {@link ContextAction} are used here.
+     * @param e the mouse event that triggered the context action. Used to
+     * position the context menu.
+     */
+    void showContextActions(List<ContextAction> actions, MouseEvent e);
     
     JFrame getTopFrame();
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Tue Jan 22 16:15:32 2013 +0100
@@ -93,7 +93,9 @@
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.swing.EdtHelper;
@@ -328,7 +330,7 @@
 
     private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
 
-    private ThermostatPopupMenu vmContextMenu;
+    private ThermostatPopupMenu contextMenu;
     private StatusBar statusBar;
     
     private final DefaultMutableTreeNode publishedRoot =
@@ -505,29 +507,27 @@
     }
 
     private void registerContextActionListener(JTree agentVmTree2) {
-        vmContextMenu = new ThermostatPopupMenu();
+        contextMenu = new ThermostatPopupMenu();
         agentVmTree2.addMouseListener(new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
                 if (e.isPopupTrigger()) {
                     Ref ref = getSelectedHostOrVm();
-                    if (ref instanceof VmRef) {
-                        fireViewAction(Action.SHOW_VM_CONTEXT_MENU, e);
-                    }
+                    fireViewAction(Action.SHOW_HOST_VM_CONTEXT_MENU, e);
                 }
             }
         });
     }
 
     @Override
-    public void showVMContextActions(final List<VMContextAction> actions, final MouseEvent e) {
+    public void showContextActions(final List<ContextAction> actions, final MouseEvent e) {
         SwingUtilities.invokeLater(new Runnable() {
 
             @Override
             public void run() {
-                vmContextMenu.removeAll();
+                contextMenu.removeAll();
 
-                for (final VMContextAction action: actions) {
+                for (final ContextAction action: actions) {
                     JMenuItem contextAction = new JMenuItem();
                     contextAction.setText(action.getName());
                     contextAction.setToolTipText(action.getDescription());
@@ -535,15 +535,18 @@
                     contextAction.addActionListener(new java.awt.event.ActionListener() {
                         @Override
                         public void actionPerformed(java.awt.event.ActionEvent e) {
-                            fireViewAction(Action.VM_CONTEXT_ACTION, action);
+                            fireViewAction(Action.HOST_VM_CONTEXT_ACTION, action);
                         }
                     });
-                    vmContextMenu.add(contextAction);
+
+                    // the component name is for unit tests only
+                    contextAction.setName(action.getName());
+
+                    contextMenu.add(contextAction);
                 }
 
-                vmContextMenu.show((Component)e.getSource(), e.getX(), e.getY());
+                contextMenu.show((Component)e.getSource(), e.getX(), e.getY());
             }
-
         });
     }
     
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Tue Jan 22 16:15:32 2013 +0100
@@ -54,7 +54,9 @@
 import com.redhat.thermostat.client.core.views.AgentInformationViewProvider;
 import com.redhat.thermostat.client.core.views.ClientConfigViewProvider;
 import com.redhat.thermostat.client.core.views.ClientConfigurationView;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.swing.internal.MainView.Action;
@@ -295,10 +297,10 @@
                 case SHOW_ABOUT_DIALOG:
                     showAboutDialog();
                     break;
-                case SHOW_VM_CONTEXT_MENU:
+                case SHOW_HOST_VM_CONTEXT_MENU:
                     showContextMenu(evt);
                     break;
-                case VM_CONTEXT_ACTION:
+                case HOST_VM_CONTEXT_ACTION:
                     handleVMHooks(evt);
                     break;
                 case SHUTDOWN:
@@ -371,32 +373,49 @@
     }
 
     private void showContextMenu(ActionEvent<Action> evt) {
-        List<VMContextAction> toShow = new ArrayList<>();
-        VmRef vm = (VmRef) view.getSelectedHostOrVm();
+        List<ContextAction> toShow = new ArrayList<>();
+
+        Ref ref = view.getSelectedHostOrVm();
+        if (ref instanceof HostRef) {
+            HostRef vm = (HostRef) ref;
+
+            logger.log(Level.INFO, "registering applicable HostContextActions actions to show");
 
-        logger.log(Level.INFO, "registering applicable VMContextActions actions to show");
+            for (HostContextAction action : facadeFactory.getHostContextActions()) {
+                if (action.getFilter().matches(vm)) {
+                    toShow.add(action);
+                }
+            }
+        } else if (ref instanceof VmRef) {
+            VmRef vm = (VmRef) ref;
 
-        for (VMContextAction action : facadeFactory.getVMContextActions()) {
-            if (action.getFilter().matches(vm)) {
-                toShow.add(action);
+            logger.log(Level.INFO, "registering applicable VMContextActions actions to show");
+
+            for (VMContextAction action : facadeFactory.getVMContextActions()) {
+                if (action.getFilter().matches(vm)) {
+                    toShow.add(action);
+                }
             }
         }
 
-        view.showVMContextActions(toShow, (MouseEvent)evt.getPayload());
+        view.showContextActions(toShow, (MouseEvent) evt.getPayload());
     }
 
     private void handleVMHooks(ActionEvent<MainView.Action> event) {
         Object payload = event.getPayload();
-        if (payload instanceof VMContextAction) { 
-            try {
+        try {
+            if (payload instanceof HostContextAction) {
+                HostContextAction action = (HostContextAction) payload;
+                action.execute((HostRef) view.getSelectedHostOrVm());
+            } else if (payload instanceof VMContextAction) {
                 VMContextAction action = (VMContextAction) payload;
                 action.execute((VmRef) view.getSelectedHostOrVm());
-            } catch (Throwable error) {
-                logger.log(Level.SEVERE, "");
             }
+        } catch (Throwable error) {
+            logger.log(Level.SEVERE, "error invocating context action", error);
         }
     }
-    
+
     @Override
     public void showMainMainWindow() {
         view.showMainWindow();
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Tue Jan 22 16:15:32 2013 +0100
@@ -47,6 +47,7 @@
 import com.redhat.thermostat.client.core.views.HostInformationViewProvider;
 import com.redhat.thermostat.client.core.views.SummaryViewProvider;
 import com.redhat.thermostat.client.core.views.VmInformationViewProvider;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.ui.HostInformationController;
 import com.redhat.thermostat.client.ui.MainWindowController;
@@ -66,7 +67,8 @@
 
     private List<InformationService<HostRef>> hostInformationServices = new ArrayList<>();
     private List<InformationService<VmRef>> vmInformationServices = new ArrayList<>();
-    private Collection<VMContextAction> contextAction = new ArrayList<>();
+    private Collection<HostContextAction> hostContextActions = new ArrayList<>();
+    private Collection<VMContextAction> vmContextActions = new ArrayList<>();
 
     private BundleContext context;
     private ApplicationService appSvc;
@@ -136,13 +138,28 @@
     }
 
     @Override
+    public Collection<HostContextAction> getHostContextActions() {
+        return hostContextActions;
+    }
+
+    @Override
+    public void addHostContextAction(HostContextAction action) {
+        hostContextActions.add(action);
+    }
+
+    @Override
+    public void removeHostContextAction(HostContextAction action) {
+        hostContextActions.remove(action);
+    }
+
+    @Override
     public Collection<VMContextAction> getVMContextActions() {
-        return contextAction;
+        return vmContextActions;
     }
 
     @Override
     public void addVMContextAction(VMContextAction service) {
-        contextAction.add(service);
+        vmContextActions.add(service);
     }
 
     @Override
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ContextActionServiceProvider.java	Tue Jan 22 16:14:48 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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.client.swing.internal.osgi;
-
-import com.redhat.thermostat.client.osgi.service.ContextAction;
-
-public class ContextActionServiceProvider implements ContextAction {
-  
-    @Override
-    public String getName() {
-        return "system context";
-    }
-
-    @Override
-    public String getDescription() {
-        return "system context";
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTracker.java	Tue Jan 22 16:15:32 2013 +0100
@@ -0,0 +1,73 @@
+/*
+ * 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.client.swing.internal.osgi;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
+import com.redhat.thermostat.client.ui.UiFacadeFactory;
+
+@SuppressWarnings("rawtypes")
+class HostContextActionServiceTracker extends ServiceTracker {
+
+    private UiFacadeFactory uiFacadeFactory;
+
+    private BundleContext context;
+
+    @SuppressWarnings("unchecked")
+    HostContextActionServiceTracker(BundleContext context, UiFacadeFactory uiFacadeFactory) {
+        super(context, HostContextAction.class.getName(), null);
+        this.context = context;
+        this.uiFacadeFactory = uiFacadeFactory;
+    }
+
+    @Override
+    public Object addingService(ServiceReference reference) {
+        @SuppressWarnings("unchecked")
+        HostContextAction service = (HostContextAction) super.addingService(reference);
+        uiFacadeFactory.addHostContextAction(service);
+        return service;
+    }
+
+    @Override
+    public void removedService(ServiceReference reference, Object service) {
+        uiFacadeFactory.removeHostContextAction((HostContextAction)service);
+        super.removedService(reference, service);
+    }
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Tue Jan 22 16:15:32 2013 +0100
@@ -70,7 +70,8 @@
 public class ThermostatActivator implements BundleActivator {
 
     private InformationServiceTracker infoServiceTracker;
-    private VMContextActionServiceTracker contextActionTracker;
+    private HostContextActionServiceTracker hostContextActionTracker;
+    private VMContextActionServiceTracker vmContextActionTracker;
 
     private CommandRegistry cmdReg;
 
@@ -111,8 +112,12 @@
 
                 infoServiceTracker = new InformationServiceTracker(context, uiFacadeFactory);
                 infoServiceTracker.open();
-                contextActionTracker = new VMContextActionServiceTracker(context, uiFacadeFactory);
-                contextActionTracker.open();
+
+                hostContextActionTracker = new HostContextActionServiceTracker(context, uiFacadeFactory);
+                hostContextActionTracker.open();
+
+                vmContextActionTracker = new VMContextActionServiceTracker(context, uiFacadeFactory);
+                vmContextActionTracker.open();
 
                 cmdReg = new CommandRegistryImpl(context);
                 Main main = new Main(keyring, uiFacadeFactory, new String[0]);
@@ -129,7 +134,8 @@
     @Override
     public void stop(BundleContext context) throws Exception {
         infoServiceTracker.close();
-        contextActionTracker.close();
+        hostContextActionTracker.close();
+        vmContextActionTracker.close();
         cmdReg.unregisterCommands();
     }
 }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/GUIClientCommandTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/GUIClientCommandTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Red Hat, Inc.
+ * Copyright 2013 Red Hat, Inc.
  *
  * This file is part of Thermostat.
  *
@@ -39,23 +39,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNotNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.util.Dictionary;
-
 import org.apache.commons.cli.Options;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.osgi.framework.BundleContext;
 
-import com.redhat.thermostat.client.osgi.service.ContextAction;
-import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandContextFactory;
 import com.redhat.thermostat.common.cli.CommandException;
@@ -68,8 +60,7 @@
     @Before
     public void setUp() {
         clientMain = mock(Main.class);
-        BundleContext ctxt = mock(BundleContext.class);
-        cmd = new GUIClientCommand(clientMain, ctxt);
+        cmd = new GUIClientCommand(clientMain);
     }
 
     @After
@@ -80,17 +71,15 @@
 
     @Test
     public void testRun() throws CommandException {
-        BundleContext bCtx = mock(BundleContext.class);
         CommandContextFactory cmdCtxFactory = mock(CommandContextFactory.class);
 
         CommandContext cmdCtx = mock(CommandContext.class);
         when(cmdCtx.getCommandContextFactory()).thenReturn(cmdCtxFactory);
 
-        cmd = new GUIClientCommand(clientMain, bCtx);
+        cmd = new GUIClientCommand(clientMain);
         cmd.run(cmdCtx);
 
         verify(clientMain).run();
-        verify(bCtx).registerService(eq(ContextAction.class.getName()), isNotNull(), any(Dictionary.class));
     }
 
     @Test
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -66,7 +66,9 @@
 
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.ui.SummaryController;
@@ -103,8 +105,9 @@
     private HostInfoDAO mockHostsDAO;
     private VmInfoDAO mockVmsDAO;
 
-    private VMContextAction action1;
-    private VMContextAction action2;
+    private HostContextAction hostContextAction1;
+    private VMContextAction vmContextAction1;
+    private VMContextAction vmContextAction2;
 
     private HostFilterRegistry hostFilterRegistry;
     private VmFilterRegistry vmFilterRegistry;
@@ -177,6 +180,7 @@
         ArgumentCaptor<ActionListener> grabInfoRegistry = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(vmInfoRegistry).addActionListener(grabInfoRegistry.capture());
 
+        setUpHostContextActions();
         setUpVMContextActions();
 
         controller = new MainWindowControllerImpl(appSvc, uiFacadeFactory, view, registryFactory, mockHostsDAO, mockVmsDAO);
@@ -187,26 +191,38 @@
         decoratorsListener = grabDecoratorsListener.getValue();
     }
 
+    private void setUpHostContextActions() {
+        hostContextAction1 = mock(HostContextAction.class);
+        Filter<HostRef> hostFilter1 = mock(Filter.class);
+        when(hostFilter1.matches(isA(HostRef.class))).thenReturn(true);
+
+        when(hostContextAction1.getName()).thenReturn("action1");
+        when(hostContextAction1.getDescription()).thenReturn("action1desc");
+        when(hostContextAction1.getFilter()).thenReturn(hostFilter1);
+
+        when(uiFacadeFactory.getHostContextActions()).thenReturn(Arrays.asList(hostContextAction1));
+    }
+
     private void setUpVMContextActions() {
-        action1 = mock(VMContextAction.class);
+        vmContextAction1 = mock(VMContextAction.class);
         Filter action1Filter = mock(Filter.class);
         when(action1Filter.matches(isA(VmRef.class))).thenReturn(true);
 
-        when(action1.getName()).thenReturn("action1");
-        when(action1.getDescription()).thenReturn("action1desc");
-        when(action1.getFilter()).thenReturn(action1Filter);
+        when(vmContextAction1.getName()).thenReturn("action1");
+        when(vmContextAction1.getDescription()).thenReturn("action1desc");
+        when(vmContextAction1.getFilter()).thenReturn(action1Filter);
         
-        action2 = mock(VMContextAction.class);
+        vmContextAction2 = mock(VMContextAction.class);
         Filter action2Filter = mock(Filter.class);
         when(action2Filter.matches(isA(VmRef.class))).thenReturn(false);
 
-        when(action2.getName()).thenReturn("action2");
-        when(action2.getDescription()).thenReturn("action2desc");
-        when(action2.getFilter()).thenReturn(action2Filter);
+        when(vmContextAction2.getName()).thenReturn("action2");
+        when(vmContextAction2.getDescription()).thenReturn("action2desc");
+        when(vmContextAction2.getFilter()).thenReturn(action2Filter);
         
         Collection<VMContextAction> actions = new ArrayList<>();
-        actions.add(action1);
-        actions.add(action2);
+        actions.add(vmContextAction1);
+        actions.add(vmContextAction2);
         
         when(uiFacadeFactory.getVMContextActions()).thenReturn(actions);
     }
@@ -495,7 +511,24 @@
 
         assertEquals(2, id);
     }
-    
+
+    @Test
+    public void verifyHostActionsAreShown() {
+        HostRef host = mock(HostRef.class);
+        when(view.getSelectedHostOrVm()).thenReturn(host);
+
+        MouseEvent uiEvent = mock(MouseEvent.class);
+        ActionEvent<MainView.Action> viewEvent = new ActionEvent<>(view, MainView.Action.SHOW_HOST_VM_CONTEXT_MENU);
+        viewEvent.setPayload(uiEvent);
+
+        l.actionPerformed(viewEvent);
+
+        List<ContextAction> actions = new ArrayList<>();
+        actions.add(hostContextAction1);
+
+        verify(view).showContextActions(actions, uiEvent);
+    }
+
     @Test
     public void verityVMActionsAreShown() {
         VmInfo vmInfo = new VmInfo(0, 1, 2, null, null, null, null, null, null, null, null, null, null, null);
@@ -505,26 +538,41 @@
         when(view.getSelectedHostOrVm()).thenReturn(ref);
 
         MouseEvent uiEvent = mock(MouseEvent.class);
-        ActionEvent<MainView.Action> viewEvent = new ActionEvent<>(view, MainView.Action.SHOW_VM_CONTEXT_MENU);
+        ActionEvent<MainView.Action> viewEvent = new ActionEvent<>(view, MainView.Action.SHOW_HOST_VM_CONTEXT_MENU);
         viewEvent.setPayload(uiEvent);
 
         l.actionPerformed(viewEvent);
 
-        verify(view).showVMContextActions(Arrays.asList(action1), uiEvent);
+        List<ContextAction> actions = new ArrayList<>();
+        actions.add(vmContextAction1);
+
+        verify(view).showContextActions(actions, uiEvent);
     }
-    
+
+    @Test
+    public void verifyHostActionsAreExecuted() {
+        HostRef hostRef = mock(HostRef.class);
+        when(view.getSelectedHostOrVm()).thenReturn(hostRef);
+
+        ActionEvent<MainView.Action> event = new ActionEvent<>(view, MainView.Action.HOST_VM_CONTEXT_ACTION);
+        event.setPayload(hostContextAction1);
+        l.actionPerformed(event);
+
+        verify(hostContextAction1, times(1)).execute(hostRef);
+    }
+
     @Test
     public void verityVMActionsAreExecuted() {
 
         VmRef vmRef = mock(VmRef.class);
         when(view.getSelectedHostOrVm()).thenReturn(vmRef);
 
-        ActionEvent<MainView.Action> event = new ActionEvent<>(view, MainView.Action.VM_CONTEXT_ACTION);
-        event.setPayload(action1);
+        ActionEvent<MainView.Action> event = new ActionEvent<>(view, MainView.Action.HOST_VM_CONTEXT_ACTION);
+        event.setPayload(vmContextAction1);
         l.actionPerformed(event);
         
-        verify(action1, times(1)).execute(any(VmRef.class));
-        verify(action2, times(0)).execute(any(VmRef.class));
+        verify(vmContextAction1, times(1)).execute(any(VmRef.class));
+        verify(vmContextAction2, times(0)).execute(any(VmRef.class));
     }
 
     @Test
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -39,6 +39,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.atLeastOnce;
@@ -47,6 +48,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.awt.event.MouseEvent;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -71,13 +73,14 @@
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.DecoratorProvider;
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.swing.components.SearchField;
-import com.redhat.thermostat.client.swing.internal.MainView;
-import com.redhat.thermostat.client.swing.internal.MainWindow;
 import com.redhat.thermostat.client.ui.Decorator;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
@@ -354,4 +357,38 @@
         assertEquals(null, window.getSelectedHostOrVm());
     }
 
+    @GUITest
+    @Test
+    public void verifyContextMenu() {
+        List<ContextAction> actions = new ArrayList<>();
+
+        HostContextAction action = mock(HostContextAction.class);
+        when(action.getName()).thenReturn("action");
+        Filter allMatchingFilter = mock(Filter.class);
+        when(allMatchingFilter.matches(any(HostRef.class))).thenReturn(true);
+
+        actions.add(action);
+
+        frameFixture.show();
+
+        // add a second action listener to discard the 'show' event invoked on the first
+        l = mock(ActionListener.class);
+        window.addActionListener(l);
+
+        MouseEvent e = new MouseEvent(window, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), MouseEvent.BUTTON2_MASK, 0, 0, 0, 0, 1, true, MouseEvent.BUTTON2);
+
+        window.showContextActions(actions, e);
+
+        JMenuItemFixture hostActionMenuItem = frameFixture.menuItem("action");
+        hostActionMenuItem.click();
+
+        ArgumentCaptor<ActionEvent> actionEventCaptor = ArgumentCaptor.forClass(ActionEvent.class);
+        verify(l).actionPerformed(actionEventCaptor.capture());
+
+        ActionEvent actionEvent = actionEventCaptor.getValue();
+        assertEquals(window, actionEvent.getSource());
+        assertEquals(MainView.Action.HOST_VM_CONTEXT_ACTION, actionEvent.getActionId());
+        assertEquals(action, actionEvent.getPayload());
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTrackerTest.java	Tue Jan 22 16:15:32 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.client.swing.internal.osgi;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Test;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.osgi.service.HostContextAction;
+import com.redhat.thermostat.client.ui.UiFacadeFactory;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class HostContextActionServiceTrackerTest {
+
+    @Test
+    public void verifyHostActionIsAddedToAndRemovedFromUiModel() {
+        StubBundleContext bundleContext = new StubBundleContext();
+        UiFacadeFactory applicationModel = mock(UiFacadeFactory.class);
+
+        HostContextAction hostAction = mock(HostContextAction.class);
+        ServiceRegistration registration = bundleContext.registerService(HostContextAction.class, hostAction, null);
+
+        HostContextActionServiceTracker tracker = new HostContextActionServiceTracker(bundleContext, applicationModel);
+        tracker.open();
+
+        registration.unregister();
+
+        tracker.close();
+
+        verify(applicationModel).addHostContextAction(hostAction);
+        verify(applicationModel).removeHostContextAction(hostAction);
+    }
+
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivatorTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -73,5 +73,7 @@
         assertTrue(ctx.isServiceRegistered(ClientConfigViewProvider.class.getName(), SwingClientConfigurationViewProvider.class));
         
         assertEquals(6, ctx.getAllServices().size());
+
+        // FIXME add more tests for the service tracker used by ThermostatActivator
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Pair.java	Tue Jan 22 16:15:32 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.common;
+
+import java.util.Objects;
+
+/**
+ * A container that hold two values.
+ * <p>
+ * The values may be related or unrelated.
+ * <p>
+ * For most predictable results, the params should be immutable. If the value of
+ * {@link #hashCode()} is relevant, the two values must provide sane
+ * implementations of hashCode too.
+ *
+ * @param <F> the type of the first value
+ * @param <S> the type of the second value
+ */
+public class Pair<F, S> {
+
+    private final F first;
+    private final S second;
+
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    public F getFirst() {
+        return first;
+    }
+
+    public S getSecond() {
+        return second;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Pair)) {
+            return false;
+        }
+        Pair<?,?> other = (Pair<?,?>) obj;
+        return Objects.equals(first, other.first) && Objects.equals(second, other.second);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(first, second);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/PairTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -0,0 +1,78 @@
+/*
+ * 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.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class PairTest {
+
+    @Test
+    public void testCreateAndExtractParts() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+
+        assertEquals("1", pair.getFirst());
+        assertEquals("2", pair.getSecond());
+    }
+
+    @Test
+    public void testEquals() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+        Pair<String, String> samePair = new Pair<>("1", "2");
+
+        Pair<String, String> differentPair = new Pair<>("2", "1");
+
+        assertFalse(pair.equals(null));
+        assertTrue(pair.equals(samePair));
+        assertTrue(samePair.equals(pair));
+        assertFalse(pair.equals(differentPair));
+        assertFalse(pair.equals(new Object()));
+    }
+
+    @Test
+    public void testHashCode() {
+        Pair<String, String> pair = new Pair<>("1", "2");
+        Pair<String, String> samePair = new Pair<>("1", "2");
+        Pair<String, String> differentPair = new Pair<>("lol", "code");
+
+        assertFalse(pair.hashCode() == differentPair.hashCode());
+        assertTrue(pair.hashCode() == samePair.hashCode());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/scripts/thermostat-client-gui-debug	Tue Jan 22 16:15:32 2013 +0100
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# 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.
+#
+#####################################################################
+#
+# Some necessary variables.
+if [ x"$THERMOSTAT_HOME" = x ] ; then
+  THERMOSTAT_HOME="@thermostat.home@"
+fi
+export THERMOSTAT_HOME
+
+if [ x"$THERMOSTAT_CLIENT_DEBUG_ADDRESS" = x ] ; then
+  THERMOSTAT_CLIENT_DEBUG_ADDRESS=1080
+fi
+export THERMOSTAT_CLIENT_DEBUG_ADDRESS
+
+$THERMOSTAT_HOME/bin/thermostat gui -J-Dsun.awt.disablegrab=true -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,address=$THERMOSTAT_CLIENT_DEBUG_ADDRESS
+exit $?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/scripts/thermostat-client-service-debug	Tue Jan 22 16:15:32 2013 +0100
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# 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.
+#
+#####################################################################
+#
+# Some necessary variables.
+if [ x"$THERMOSTAT_HOME" = x ] ; then
+  THERMOSTAT_HOME="@thermostat.home@"
+fi
+export THERMOSTAT_HOME
+
+if [ x"$THERMOSTAT_CLIENT_DEBUG_ADDRESS" = x ] ; then
+  THERMOSTAT_SERVICE_DEBUG_ADDRESS=1081
+fi
+export THERMOSTAT_SERVICE_DEBUG_ADDRESS
+
+$THERMOSTAT_HOME/bin/thermostat service -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,address=$THERMOSTAT_SERVICE_DEBUG_ADDRESS
+exit $?
--- a/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/internal/Activator.java	Tue Jan 22 16:15:32 2013 +0100
@@ -42,7 +42,6 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
-import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
@@ -59,7 +58,6 @@
         Class<?>[] serviceDeps = new Class<?>[] {
             AgentInfoDAO.class,
             VmInfoDAO.class,
-            ContextAction.class,
         };
 
         killVmActionTracker = new MultipleServiceTracker(context, serviceDeps, new Action() {
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Tue Jan 22 16:15:32 2013 +0100
@@ -51,7 +51,7 @@
         LESS_THAN,
         LESS_THAN_OR_EQUAL_TO,
     }
-
+    
     enum SortDirection {
         ASCENDING(1),
         DESCENDING(-1);
@@ -68,7 +68,7 @@
     }
 
     <S> void where(Key<S> key, Criteria criteria, S value);
-
+    
     void sort(Key<?> key, SortDirection direction);
 
     void limit(int n);
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Tue Jan 22 16:15:32 2013 +0100
@@ -36,9 +36,13 @@
 
 package com.redhat.thermostat.storage.mongodb.internal;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
 import com.mongodb.DBObject;
 import com.redhat.thermostat.storage.core.AbstractQuery;
 import com.redhat.thermostat.storage.core.Category;
@@ -50,6 +54,8 @@
 
     private MongoStorage storage;
     private BasicDBObject query = new BasicDBObject();
+    private Map<String, BasicDBObjectBuilder> builerMap;
+    
     private boolean hasClauses = false;
     private Category<T> category;
     private Class<T> resultClass;
@@ -58,6 +64,7 @@
         this.storage = storage;
         this.category = category;
         this.resultClass = category.getDataClass();
+        this.builerMap = new HashMap<String, BasicDBObjectBuilder>();
     }
 
     public Category<T> getCategory() {
@@ -74,31 +81,45 @@
     }
 
     public void where(String key, Criteria operator, Object value) {
-        switch (operator) {
-        case EQUALS:
+
+        // strict equality is mutually exclusive on the key
+        if (operator.equals(Criteria.EQUALS)) {
             query.put(key, value);
-            break;
-
-        case NOT_EQUAL_TO:
-            query.put(key, new BasicDBObject("$ne", value));
-            break;
-
-        case LESS_THAN:
-            query.put(key, new BasicDBObject("$lt", value));
-            break;
-
-        case LESS_THAN_OR_EQUAL_TO:
-            query.put(key, new BasicDBObject("$lte", value));
-            break;
-        case GREATER_THAN:
-            query.put(key, new BasicDBObject("$gt", value));
-            break;
-
-        case GREATER_THAN_OR_EQUAL_TO:
-            query.put(key, new BasicDBObject("$gte", value));
-            break;
-        default:
-            throw new IllegalArgumentException("MongoQuery can not handle " + operator);
+        
+        } else {
+            BasicDBObjectBuilder queryParameters = null;
+            if (builerMap.containsKey(key)) {
+                queryParameters = (BasicDBObjectBuilder) builerMap.get(key);
+            } else {
+                queryParameters = BasicDBObjectBuilder.start();
+                builerMap.put(key, queryParameters);
+            }
+            
+            switch (operator) {
+    
+            case NOT_EQUAL_TO:
+                queryParameters.add("$ne", value);
+                break;
+    
+            case LESS_THAN:
+                queryParameters.add("$lt", value);
+                break;
+    
+            case LESS_THAN_OR_EQUAL_TO:
+                queryParameters.add("$lte", value);
+                break;
+            case GREATER_THAN:
+                queryParameters.add("$gt", value);
+                break;
+    
+            case GREATER_THAN_OR_EQUAL_TO:
+                queryParameters.add("$gte", value);
+                break;
+    
+            default:
+                throw new IllegalArgumentException("MongoQuery can not handle " + operator);
+            }
+            query.put(key, queryParameters.get());
         }
         hasClauses = true;
     }
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoQueryTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoQueryTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -45,6 +45,7 @@
 import org.junit.Test;
 
 import com.mongodb.BasicDBObject;
+import com.mongodb.BasicDBObjectBuilder;
 import com.mongodb.DBObject;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Query.Criteria;
@@ -121,6 +122,48 @@
         assertEquals(new BasicDBObject("$lte", "value"), generatedQuery.get("key"));
     }
 
+    @Test
+    public void testMultiWhere() {
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
+        query.where("test", Criteria.LESS_THAN_OR_EQUAL_TO, 1);
+        query.where("test", Criteria.GREATER_THAN, 2);
+
+        DBObject generatedQuery = query.getGeneratedQuery();
+        DBObject dbObject = BasicDBObjectBuilder.start("$lte", 1).add("$gt", 2).get();
+        assertEquals(dbObject, generatedQuery.get("test"));
+    }
+    
+    @Test
+    public void testMultiWhere2() {
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
+        query.where("test", Criteria.LESS_THAN_OR_EQUAL_TO, 1);
+        query.where("test2", Criteria.GREATER_THAN, 2);
+
+        DBObject generatedQuery = query.getGeneratedQuery();
+        assertEquals(new BasicDBObject("$lte", 1), generatedQuery.get("test"));
+    }
+    
+    @Test
+    public void testMultiWhere3() {
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
+        query.where("test", Criteria.EQUALS, 1);
+        query.where("test", Criteria.GREATER_THAN, 2);
+
+        DBObject generatedQuery = query.getGeneratedQuery();
+        assertEquals(new BasicDBObject("$gt", 2), generatedQuery.get("test"));
+    }
+    
+    @Test
+    public void testMultiWhere4() {
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
+        query.where("test", Criteria.EQUALS, 1);
+        query.where("test2", Criteria.GREATER_THAN, 2);
+
+        DBObject generatedQuery = query.getGeneratedQuery();
+        assertEquals(1, generatedQuery.get("test"));
+        assertEquals(new BasicDBObject("$gt", 2), generatedQuery.get("test2"));
+    }
+    
     private DBObject generateSimpleWhereQuery(String key, Criteria criteria, Object value) {
         MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
         query.where(key, criteria, value);
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Tue Jan 22 16:15:32 2013 +0100
@@ -38,42 +38,29 @@
 
 import java.awt.BorderLayout;
 import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.Point;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
+
 import java.util.List;
-import java.util.Map;
 
 import javax.swing.DefaultListModel;
 import javax.swing.JList;
 import javax.swing.JPanel;
+import javax.swing.JScrollBar;
 import javax.swing.JScrollPane;
-import javax.swing.ListCellRenderer;
 import javax.swing.SwingUtilities;
-import javax.swing.SwingWorker;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
 
 import com.redhat.thermostat.client.swing.ComponentVisibleListener;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.common.model.LongRange;
-
 import com.redhat.thermostat.thread.client.common.Timeline;
-import com.redhat.thermostat.thread.client.common.TimelineInfo;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
 import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineCellRenderer;
 import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineComponent;
 import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineRulerHeader;
 import com.redhat.thermostat.thread.client.swing.impl.timeline.TimelineUtils;
-import com.redhat.thermostat.thread.model.ThreadInfoData;
 
 public class SwingThreadTimelineView extends ThreadTimelineView implements SwingComponent  {
-
-    private final String lock = new String("SwingThreadTimelineViewLock");
         
     private JPanel timeLinePanel;
     private JList<TimelineComponent> chartList;
@@ -110,6 +97,14 @@
         timeLinePanel.add(timelineLegend, BorderLayout.SOUTH);
     }
     
+    private class ScrollChangeListener implements ChangeListener {
+        @Override
+        public void stateChanged(ChangeEvent e) {
+            scrollPane.repaint();
+            header.repaint();
+        }
+    }
+    
     private JScrollPane createScrollPane() {
         scrollPane = new JScrollPane(chartList);
         scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
@@ -118,52 +113,52 @@
         long now = System.currentTimeMillis();
         header = new TimelineRulerHeader(new LongRange(now, now + TimelineUtils.STEP), scrollPane);
         scrollPane.setColumnHeaderView(header);
-        scrollPane.getHorizontalScrollBar().getModel().addChangeListener(new ChangeListener() {
+
+        ScrollChangeListener listener = new ScrollChangeListener();
+        
+        scrollPane.getHorizontalScrollBar().getModel().addChangeListener(listener);
+        scrollPane.getVerticalScrollBar().getModel().addChangeListener(listener);
+        
+        return scrollPane;
+    }
+    
+    private void handleScrollBar() {
+        SwingUtilities.invokeLater(new Runnable() {
             @Override
-            public void stateChanged(ChangeEvent e) {
-                scrollPane.repaint();
+            public void run() {        
+                JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();                
+                if (!chartModel.isEmpty()) {
+                    TimelineComponent component = chartModel.getElementAt(0);
+
+                    int extent = scrollBar.getVisibleAmount();
+                    int min = scrollBar.getMinimum();
+                    int max = component.getWidth() + (2 * TimelineUtils.INC);
+
+                    scrollBar.setValues(max - extent, extent, min, max);
+                }
             }
         });
-        scrollPane.getVerticalScrollBar().getModel().addChangeListener(new ChangeListener() {
-            @Override
-            public void stateChanged(ChangeEvent e) {
-                scrollPane.repaint();
-            }
-        });        
-        return scrollPane;
     }
     
     @Override
     public void displayStats(final List<Timeline> timelines, final LongRange range) {
+                
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
+                range.setMax(range.getMax() + (2 * TimelineUtils.STEP));
                 chartModel.removeAllElements();
                 for (Timeline timeline : timelines) {
                     chartModel.addElement(new TimelineComponent(range, timeline, scrollPane));
                 }
                 header.getRange().setMin(range.getMin());
                 header.getRange().setMax(range.getMax());
+                
+                handleScrollBar();
             }
         });
     }
     
-//    private class SelectedThreadListener implements PropertyChangeListener {
-//        @Override
-//        public void propertyChange(final PropertyChangeEvent evt) {
-//            SwingWorker<Void, Void> notifier = new SwingWorker<Void, Void>() {
-//                @Override
-//                protected Void doInBackground() throws Exception {
-//                    SwingThreadTimelineView.this.
-//                    threadTimelineNotifier.fireAction(ThreadTimelineView.ThreadTimelineViewAction.THREAD_TIMELINE_SELECTED,
-//                                                      evt.getNewValue());
-//                    return null;
-//                }
-//            };
-//            notifier.execute();
-//        }
-//    }
-    
     @Override
     public Component getUiComponent() {
         return timeLinePanel;
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/Activator.java	Tue Jan 22 16:15:32 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;
@@ -57,6 +58,9 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         Class<?>[] deps = new Class<?>[] {
                 BackendService.class,
                 VmClassStatDAO.class
@@ -67,7 +71,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmClassStatDAO vmClassStatDao = (VmClassStatDAO) services.get(VmClassStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmClassStatBackend(vmClassStatDao, version);
+                backend = new VmClassStatBackend(vmClassStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackend.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackend.java	Tue Jan 22 16:15:32 2013 +0100
@@ -37,42 +37,53 @@
 package com.redhat.thermostat.vm.classstat.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;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 
-public class VmClassStatBackend extends Backend {
+public class VmClassStatBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmClassStatBackend.class);
 
-    private VmClassStatDAO vmClassStats;
-    private HostIdentifier hostId;
+    private final VmClassStatDAO vmClassStats;
+    private final VmStatusListenerRegistrar registrar;
+
+    private final Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToVmAndListener = new HashMap<>();
+
     private MonitoredHost host;
-    private VmClassStatHostListener hostListener;
+
     private boolean started;
 
-    public VmClassStatBackend(VmClassStatDAO vmClassStatDAO, Version version) {
+    public VmClassStatBackend(VmClassStatDAO vmClassStatDAO, Version version, VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM Classes Backend", VmClassStatBackend.class.getName()));
         this.vmClassStats = vmClassStatDAO;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers class loading 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 VmClassStatHostListener(vmClassStats, attachToNewProcessByDefault());
         } catch (MonitorException me) {
             LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
         } catch (URISyntaxException use) {
@@ -80,29 +91,24 @@
         }
     }
 
+    /*
+     * Methods from Backend
+     */
+
     @Override
     public boolean activate() {
         if (!started && host != null) {
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
-
+            registrar.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, "Failed to remove host listener");
-            }
+        if (started) {
+            registrar.unregister(this);
+            started = false;
         }
         return !started;
     }
@@ -128,10 +134,69 @@
     }
 
     /*
+     *  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))));
+                VmClassStatVmListener listener = new VmClassStatVmListener(vmClassStats, pid);
+                vm.addVmListener(listener);
+
+                pidToVmAndListener.put(pid, new Pair<>(vm, listener));
+                LOGGER.finer("Attached VmListener for VM: " + pid);
+            } catch (MonitorException | URISyntaxException e) {
+                LOGGER.log(Level.WARNING, "Could not attach to new vm " + pid, e);
+            }
+        } else {
+            LOGGER.log(Level.FINE, "skipping new vm " + pid);
+        }
+    }
+
+    private void vmStopped(Integer pid) {
+        Pair<MonitoredVm, ? extends VmListener> data = pidToVmAndListener.remove(pid);
+        // if there is no data, we must never have attached to the vm. Nothing to do.
+        if (data == null) {
+            return;
+        }
+
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
+        try {
+            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;
     }
-    
+
+    /*
+     * For testing purposes only.
+     */
+    Map<Integer, Pair<MonitoredVm, ? extends VmListener>> getPidToDataMap() {
+        return pidToVmAndListener;
+    }
+
 }
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListener.java	Tue Jan 22 16:14:48 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +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.classstat.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.classstat.common.VmClassStatDAO;
-
-public class VmClassStatHostListener implements HostListener {
-
-    private static final Logger logger = LoggingUtils.getLogger(VmClassStatHostListener.class);
-
-    private boolean attachNew;
-
-    private final VmClassStatDAO vmClassStatDAO;
-
-    private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
-    private Map<MonitoredVm, VmClassStatVmListener> registeredListeners  = new ConcurrentHashMap<>();
-
-    VmClassStatHostListener(VmClassStatDAO vmClassStatDAO, boolean attachNew) {
-        this.vmClassStatDAO = vmClassStatDAO;
-        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) {
-                VmClassStatVmListener listener = new VmClassStatVmListener(vmClassStatDAO, 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, VmClassStatVmListener> getRegisteredListeners() {
-        return registeredListeners;
-    }
-}
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/ActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/ActivatorTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -92,6 +92,9 @@
         VmClassStatBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // something in core thermostat activates the backend; do it manually here
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackendTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatBackendTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -38,20 +38,30 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.net.URISyntaxException;
 
 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.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
 
@@ -59,6 +69,9 @@
     
     private VmClassStatBackend backend;
     private MonitoredHost host;
+    private VmStatusListenerRegistrar registrar;
+    private HostIdentifier hostIdentifier;
+    private MonitoredVm monitoredVm1;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
@@ -67,25 +80,139 @@
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
         
-        backend = new VmClassStatBackend(vmClassStatDao, version);
+        registrar = 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 VmClassStatBackend(vmClassStatDao, version, registrar);
         
-        host = mock(MonitoredHost.class);
         backend.setHost(host);
     }
 
     @Test
-    public void testStart() throws MonitorException {
+    public void testActivate() {
         backend.activate();
-        verify(host).addHostListener(any(HostListener.class));
+        assertTrue(backend.isActive());
+        verify(registrar).register(backend);
+    }
+
+    @Test
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.activate());
         assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStop() throws MonitorException {
-        backend.activate();
-        backend.deactivate();
-        verify(host).removeHostListener(any(HostListener.class));
+    public void testCanNotActivateWithoutMonitoredHost() {
+        backend.setHost(null);
+
+        assertFalse(backend.activate());
         assertFalse(backend.isActive());
     }
     
+    @Test
+    public void testDeactivate() {
+        backend.activate();
+        backend.deactivate();
+        verify(registrar).unregister(backend);
+        assertFalse(backend.isActive());
+    }
+
+    @Test
+    public void testDeactivateTwice() {
+        backend.activate();
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+        assertTrue(backend.deactivate());
+    }
+
+    @Test
+    public void testNewVM() throws 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(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testAlreadyRunningVM() throws 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_ACTIVE, 1);
+
+        verify(monitoredVm1).addVmListener(isA(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testStatVMGetMonitoredVmFails() throws MonitorException {
+        MonitorException monitorException = new MonitorException();
+        when(host.getMonitoredVm(isA(VmIdentifier.class))).thenThrow(monitorException);
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
+        assertFalse(backend.getPidToDataMap().containsKey(1));
+    }
+
+    @Test
+    public void testStoppedVM() throws 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(VmClassStatVmListener.class));
+    }
+
+    @Test
+    public void testUnknownVMStopped() throws URISyntaxException, MonitorException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verifyNoMoreInteractions(monitoredVm1);
+    }
+
+    @Test
+    public void testErrorRemovingVmListener() throws URISyntaxException, MonitorException {
+        int VM_PID = 1;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm1);
+        MonitorException monitorException = new MonitorException();
+        doThrow(monitorException).when(monitoredVm1).removeVmListener(isA(VmClassStatVmListener.class));
+
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(monitoredVm1).detach();
+    }
+
+    @Test
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+        assertTrue(orderValue > Ordered.ORDER_MEMORY_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_NETWORK_GROUP);
+    }
 }
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatHostListenerTest.java	Tue Jan 22 16:14:48 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.classstat.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.classstat.common.VmClassStatDAO;
-
-public class VmClassStatHostListenerTest {
-    
-    private VmClassStatHostListener hostListener;
-    private MonitoredHost host;
-    private MonitoredVm monitoredVm1;
-    private MonitoredVm monitoredVm2;
-
-    @Before
-    public void setup() throws MonitorException, URISyntaxException {
-        VmClassStatDAO vmGcStatDAO = mock(VmClassStatDAO.class);
-        hostListener = new VmClassStatHostListener(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-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -37,15 +37,20 @@
 package com.redhat.thermostat.vm.classstat.agent.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
 import sun.jvmstat.monitor.Monitor;
+import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.event.MonitorStatusChangeEvent;
 import sun.jvmstat.monitor.event.VmEvent;
 
 import com.redhat.thermostat.storage.model.VmClassStat;
@@ -57,12 +62,35 @@
     private static final Integer VM_ID = 123;
     private static final Long LOADED_CLASSES = 1234L;
 
+    private VmClassStatDAO dao;
+    private VmClassStatVmListener listener;
+
+    @Before
+    public void setUp() {
+        dao = mock(VmClassStatDAO.class);
+        listener = new VmClassStatVmListener(dao, VM_ID);
+    }
+
+    @Test
+    public void testDisconnected() {
+        VmEvent vmEvent = mock(VmEvent.class);
+
+        listener.disconnected(vmEvent);
+
+        verifyNoMoreInteractions(vmEvent, dao);
+    }
+
+    @Test
+    public void testMonitorStatusChanged() {
+        MonitorStatusChangeEvent statusChangeEvent = mock(MonitorStatusChangeEvent.class);
+
+        listener.monitorStatusChanged(statusChangeEvent);
+
+        verifyNoMoreInteractions(statusChangeEvent, dao);
+    }
+
     @Test
     public void testMonitorUpdatedClassStat() throws Exception {
-
-        VmClassStatDAO dao = mock(VmClassStatDAO.class);
-
-        VmClassStatVmListener l = new VmClassStatVmListener(dao, VM_ID);
         VmEvent vmEvent = mock(VmEvent.class);
         MonitoredVm monitoredVm = mock(MonitoredVm.class);
         Monitor m = mock(Monitor.class);
@@ -70,7 +98,7 @@
         when(monitoredVm.findByName("java.cls.loadedClasses")).thenReturn(m);
         when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
 
-        l.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
 
         ArgumentCaptor<VmClassStat> arg = ArgumentCaptor.forClass(VmClassStat.class);
         verify(dao).putVmClassStat(arg.capture());
@@ -81,10 +109,6 @@
 
     @Test
     public void testMonitorUpdatedClassStatTwice() throws Exception {
-
-        VmClassStatDAO dao = mock(VmClassStatDAO.class);
-
-        VmClassStatVmListener l = new VmClassStatVmListener(dao, VM_ID);
         VmEvent vmEvent = mock(VmEvent.class);
         MonitoredVm monitoredVm = mock(MonitoredVm.class);
         Monitor m = mock(Monitor.class);
@@ -92,10 +116,24 @@
         when(monitoredVm.findByName("java.cls.loadedClasses")).thenReturn(m);
         when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
 
-        l.monitorsUpdated(vmEvent);
-        l.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
+        listener.monitorsUpdated(vmEvent);
 
         // This checks a bug where the Category threw an IllegalStateException because the DAO
         // created a new one on each call, thus violating the unique guarantee of Category.
     }
+
+    @Test
+    public void testMonitorUpdateFails() throws MonitorException {
+        VmEvent vmEvent = mock(VmEvent.class);
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+        MonitorException monitorException = new MonitorException();
+
+        when(monitoredVm.findByName(anyString())).thenThrow(monitorException);
+        when(vmEvent.getMonitoredVm()).thenReturn(monitoredVm);
+
+        listener.monitorsUpdated(vmEvent);
+
+        verifyNoMoreInteractions(dao);
+    }
 }
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/Activator.java	Tue Jan 22 16:15:32 2013 +0100
@@ -44,6 +44,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;
@@ -60,6 +61,8 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         executor = Executors.newSingleThreadScheduledExecutor();
 
         Class<?>[] deps = new Class<?>[] {
@@ -72,7 +75,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmCpuStatDAO vmCpuStatDao = (VmCpuStatDAO) services.get(VmCpuStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmCpuBackend(executor, vmCpuStatDao, version);
+                backend = new VmCpuBackend(executor, vmCpuStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackend.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackend.java	Tue Jan 22 16:15:32 2013 +0100
@@ -38,16 +38,15 @@
 
 import java.io.BufferedReader;
 import java.io.IOException;
-import java.net.URISyntaxException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 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 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;
@@ -60,7 +59,7 @@
 import com.redhat.thermostat.utils.SysConf;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
 
-public class VmCpuBackend extends Backend {
+public class VmCpuBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmCpuBackend.class);
     static final long PROC_CHECK_INTERVAL = 1000; // TODO make this configurable.
@@ -68,15 +67,17 @@
     private VmCpuStatBuilder vmCpuStatBuilder;
     private VmCpuStatDAO vmCpuStats;
     private ScheduledExecutorService executor;
-    private HostIdentifier hostId;
-    private MonitoredHost host;
-    private VmCpuHostListener hostListener;
+    private VmStatusListenerRegistrar registrar;
     private boolean started;
 
-    public VmCpuBackend(ScheduledExecutorService executor, VmCpuStatDAO vmCpuStatDao, Version version) {
+    private final List<Integer> pidsToMonitor = new CopyOnWriteArrayList<>();
+
+    public VmCpuBackend(ScheduledExecutorService executor, VmCpuStatDAO vmCpuStatDao, Version version,
+            VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM CPU Backend", VmCpuBackend.class.getName()));
         this.executor = executor;
         this.vmCpuStats = vmCpuStatDao;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers CPU statistics about a JVM");
@@ -88,25 +89,17 @@
         ProcessStatusInfoBuilder builder = new ProcessStatusInfoBuilder(new ProcDataSource());
         int numCpus = getCpuCount(source);
         vmCpuStatBuilder = new VmCpuStatBuilder(clock, numCpus, ticksPerSecond, builder);
-        
-        try {
-            hostId = new HostIdentifier((String) null);
-            host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener = new VmCpuHostListener(vmCpuStatBuilder);
-        } catch (MonitorException me) {
-            LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
-        } catch (URISyntaxException use) {
-            LOGGER.log(Level.WARNING, "Failed to create host identifier", use);
-        }
     }
 
     @Override
     public boolean activate() {
-        if (!started && host != null) {
+        if (!started) {
+            registrar.register(this);
+
             executor.scheduleAtFixedRate(new Runnable() {
                 @Override
                 public void run() {
-                    for (Integer pid : hostListener.getPidsToMonitor()) {
+                    for (Integer pid : pidsToMonitor) {
                         if (vmCpuStatBuilder.knowsAbout(pid)) {
                             VmCpuStat dataBuilt = vmCpuStatBuilder.build(pid);
                             if (dataBuilt != null) {
@@ -119,28 +112,18 @@
                 }
             }, 0, PROC_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
 
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
-
+            started = true;
         }
         return started;
     }
 
     @Override
     public boolean deactivate() {
-        if (started && host != null) {
+        if (started) {
             executor.shutdown();
+            registrar.unregister(this);
 
-            try {
-                host.removeHostListener(hostListener);
-                started = false;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.INFO, "Failed to remove host listener");
-            }
+            started = false;
         }
         return !started;
     }
@@ -183,10 +166,23 @@
     }
 
     /*
-     * For testing purposes only.
+     * Methods implementing VmStatusListener
      */
-    void setHost(MonitoredHost host) {
-        this.host = host;
+    @Override
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            pidsToMonitor.add(pid);
+            break;
+        case VM_STOPPED:
+            // the cast is important because it changes the call from remove(index) to remove(Object)
+            pidsToMonitor.remove((Integer) pid);
+            vmCpuStatBuilder.forgetAbout(pid);
+            break;
+        }
+
     }
     
     /*
@@ -195,12 +191,5 @@
     void setVmCpuStatBuilder(VmCpuStatBuilder vmCpuStatBuilder) {
         this.vmCpuStatBuilder = vmCpuStatBuilder;
     }
-    
-    /*
-     * For testing purposes only.
-     */
-    void setHostListener(VmCpuHostListener hostListener) {
-        this.hostListener = hostListener;
-    }
-    
+
 }
--- a/vm-cpu/agent/src/main/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListener.java	Tue Jan 22 16:14:48 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +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.cpu.agent.internal;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.logging.Logger;
-
-import sun.jvmstat.monitor.event.HostEvent;
-import sun.jvmstat.monitor.event.HostListener;
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-
-public class VmCpuHostListener implements HostListener {
-    
-    private static final Logger LOGGER = LoggingUtils.getLogger(VmCpuHostListener.class);
-    
-    private final Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<Integer>();
-    private VmCpuStatBuilder vmCpuStatBuilder;
-    
-    public VmCpuHostListener(VmCpuStatBuilder builder) {
-        this.vmCpuStatBuilder = builder;
-    }
-
-    @Override
-    public void vmStatusChanged(VmStatusChangeEvent event) {
-        for (Object newVm : event.getStarted()) {
-            Integer vmId = (Integer) newVm;
-            LOGGER.fine("New vm: " + vmId);
-            pidsToMonitor.add(vmId);
-        }
-
-        for (Object stoppedVm : event.getTerminated()) {
-            Integer vmId = (Integer) stoppedVm;
-            LOGGER.fine("stopped vm: " + vmId);
-            pidsToMonitor.remove(vmId);
-            vmCpuStatBuilder.forgetAbout(vmId);
-        }
-    }
-
-    @Override
-    public void disconnected(HostEvent event) {
-        LOGGER.warning("Disconnected from host");
-    }
-    
-    public Set<Integer> getPidsToMonitor() {
-        return pidsToMonitor;
-    }
-    
-}
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/ActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/ActivatorTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -93,6 +93,9 @@
         VmCpuBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // core thermostat will activate the backend once it's registered
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackendTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuBackendTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -40,8 +40,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.HashSet;
@@ -53,10 +56,9 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import sun.jvmstat.monitor.MonitorException;
-import sun.jvmstat.monitor.MonitoredHost;
-import sun.jvmstat.monitor.event.HostListener;
-
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.storage.model.VmCpuStat;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
@@ -65,8 +67,8 @@
     
     private VmCpuBackend backend;
     private ScheduledExecutorService executor;
-    private MonitoredHost host;
     private VmCpuStatDAO vmCpuStatDao;
+    private VmStatusListenerRegistrar registrar;
 
     @Before
     public void setup() {
@@ -76,21 +78,58 @@
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
         
-        backend = new VmCpuBackend(executor, vmCpuStatDao, version);
+        registrar = mock(VmStatusListenerRegistrar.class);
         
-        host = mock(MonitoredHost.class);
-        backend.setHost(host);
+        backend = new VmCpuBackend(executor, vmCpuStatDao, version, registrar);
+    }
+
+    @Test
+    public void testActivate() {
+        backend.activate();
+
+        verify(executor).scheduleAtFixedRate(isA(Runnable.class), eq(0l), eq(1000l), eq(TimeUnit.MILLISECONDS));
+        verify(registrar).register(backend);
+        assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStart() throws MonitorException {
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.deactivate());
+    }
+
+    @Test
+    public void testDeactivate() {
+        backend.activate();
+        backend.deactivate();
+
+        verify(executor).shutdown();
+        verify(registrar).unregister(backend);
+        assertFalse(backend.isActive());
+    }
+
+
+    @Test
+    public void testDeactivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+    }
+
+    @Test
+    public void testStart() {
         // Setup Runnable mocks
         final Set<Integer> pids = new HashSet<>();
         pids.add(0);
         pids.add(1);
-        VmCpuHostListener listener = mock(VmCpuHostListener.class);
-        when(listener.getPidsToMonitor()).thenReturn(pids);
-        backend.setHostListener(listener);
         
         VmCpuStatBuilder builder = mock(VmCpuStatBuilder.class);
         VmCpuStat stat0 = mock(VmCpuStat.class);
@@ -100,11 +139,15 @@
         backend.setVmCpuStatBuilder(builder);
         
         backend.activate();
+
+        verify(registrar).register(backend);
         ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         verify(executor).scheduleAtFixedRate(captor.capture(), any(Long.class), any(Long.class), any(TimeUnit.class));
         assertTrue(backend.isActive());
-        verify(host).addHostListener(any(HostListener.class));
         
+        backend.vmStatusChanged(Status.VM_ACTIVE, 0);
+        backend.vmStatusChanged(Status.VM_STARTED, 1);
+
         Runnable runnable = captor.getValue();
         runnable.run();
         verify(builder).learnAbout(0);
@@ -114,15 +157,24 @@
         runnable.run();
         verify(vmCpuStatDao).putVmCpuStat(stat0);
         verify(vmCpuStatDao).putVmCpuStat(stat1);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, 0);
+        backend.vmStatusChanged(Status.VM_STOPPED, 1);
+
+        verify(builder).forgetAbout(0);
+        verify(builder).forgetAbout(1);
+
+        when(builder.knowsAbout(anyInt())).thenReturn(false);
+        runnable.run();
+
+        verifyNoMoreInteractions(vmCpuStatDao);
     }
-    
+
     @Test
-    public void testStop() throws MonitorException {
-        backend.activate();
-        backend.deactivate();
-        verify(executor).shutdown();
-        assertFalse(backend.isActive());
-        verify(host).removeHostListener(any(HostListener.class));
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+
+        assertTrue(orderValue > Ordered.ORDER_CPU_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_MEMORY_GROUP);
     }
-    
 }
--- a/vm-cpu/agent/src/test/java/com/redhat/thermostat/vm/cpu/agent/internal/VmCpuHostListenerTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +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.cpu.agent.internal;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import sun.jvmstat.monitor.event.VmStatusChangeEvent;
-
-public class VmCpuHostListenerTest {
-    
-    private VmCpuHostListener hostListener;
-    private VmCpuStatBuilder builder;
-
-    @Before
-    public void setup() {
-        builder = mock(VmCpuStatBuilder.class);
-        
-        hostListener = new VmCpuHostListener(builder);
-    }
-
-    @Test
-    public void testNewVM() throws InterruptedException {
-        startVMs();
-        
-        // Check that pids are added to set
-        Set<Integer> pids = hostListener.getPidsToMonitor();
-        assertTrue(pids.contains(1));
-        assertTrue(pids.contains(2));
-    }
-    
-    @Test
-    public void testStoppedVM() throws InterruptedException {
-        final Set<Integer> stopped = new HashSet<>();
-        stopped.add(1);
-        
-        startVMs();
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getStarted()).thenReturn(Collections.emptySet());
-        when(event.getTerminated()).thenReturn(stopped);
-        hostListener.vmStatusChanged(event);
-        
-        // Ensure only 1 removed
-        verify(builder).forgetAbout(1);
-        verify(builder, never()).forgetAbout(2);
-    }
-
-    private void startVMs() throws InterruptedException {
-        final Set<Integer> started = new HashSet<>();
-        started.add(1);
-        started.add(2);
-        
-        // Trigger a change event
-        VmStatusChangeEvent event = mock(VmStatusChangeEvent.class);
-        when(event.getStarted()).thenReturn(started);
-        when(event.getTerminated()).thenReturn(Collections.emptySet());
-        hostListener.vmStatusChanged(event);
-    }
-    
-}
--- a/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/VmGcBackend.java	Tue Jan 22 16:15:32 2013 +0100
@@ -54,6 +54,7 @@
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendID;
 import com.redhat.thermostat.backend.BackendsProperties;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
@@ -65,7 +66,7 @@
     private final VmGcStatDAO vmGcStats;
     private final VmStatusListenerRegistrar registerer;
 
-    private final Map<Integer, VmAndListener> registeredListeners = new HashMap<>();
+    private final Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToData = new HashMap<>();
     private MonitoredHost host;
     private boolean started;
 
@@ -92,7 +93,7 @@
 
     @Override
     public boolean activate() {
-        if (!started) {
+        if (!started && host != null) {
             registerer.register(this);
             started = true;
         }
@@ -151,7 +152,7 @@
                 if (vm != null) {
                     VmGcVmListener listener = new VmGcVmListener(vmGcStats, pid);
                     vm.addVmListener(listener);
-                    registeredListeners.put(pid, new VmAndListener(vm, listener));
+                    pidToData.put(pid, new Pair<>(vm, listener));
                     LOGGER.finer("Attached VmListener for VM: " + pid);
                 } else {
                     LOGGER.warning("could not connect to vm " + pid);
@@ -165,14 +166,14 @@
     }
 
     private void vmStopped(int pid) {
-        VmAndListener tuple = registeredListeners.remove(pid);
-        if (tuple == null) {
-            LOGGER.warning("received vm stopped for an unknown VM");
+        Pair<MonitoredVm, ? extends VmListener> data = pidToData.remove(pid);
+        // if there is no data, we must never have attached to it. Nothing to do.
+        if (data == null) {
             return;
         }
 
-        MonitoredVm vm = tuple.vm;
-        VmListener listener = tuple.listener;
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
         try {
             if (listener != null) {
                 vm.removeVmListener(listener);
@@ -190,14 +191,4 @@
         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-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/Activator.java	Tue Jan 22 16:15:32 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;
@@ -57,6 +58,9 @@
     
     @Override
     public void start(final BundleContext context) throws Exception {
+
+        final VmStatusListenerRegistrar registrar = new VmStatusListenerRegistrar(context);
+
         Class<?>[] deps = new Class<?>[] {
                 BackendService.class,
                 VmMemoryStatDAO.class
@@ -67,7 +71,7 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 VmMemoryStatDAO vmMemoryStatDao = (VmMemoryStatDAO) services.get(VmMemoryStatDAO.class.getName());
                 Version version = new Version(context.getBundle());
-                backend = new VmMemoryBackend(vmMemoryStatDao, version);
+                backend = new VmMemoryBackend(vmMemoryStatDao, version, registrar);
                 reg = context.registerService(Backend.class.getName(), backend, null);
             }
 
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackend.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackend.java	Tue Jan 22 16:15:32 2013 +0100
@@ -37,42 +37,51 @@
 package com.redhat.thermostat.vm.memory.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;
+import com.redhat.thermostat.common.Pair;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
-public class VmMemoryBackend extends Backend {
+public class VmMemoryBackend extends Backend implements VmStatusListener {
 
     private static final Logger LOGGER = LoggingUtils.getLogger(VmMemoryBackend.class);
 
     private VmMemoryStatDAO vmMemoryStats;
-    private HostIdentifier hostId;
     private MonitoredHost host;
-    private VmMemoryHostListener hostListener;
+    private final VmStatusListenerRegistrar registrar;
+
     private boolean started;
+    private Map<Integer, Pair<MonitoredVm, ? extends VmListener>> pidToData = new HashMap<>();
 
-    public VmMemoryBackend(VmMemoryStatDAO vmMemoryStatDAO, Version version) {
+    public VmMemoryBackend(VmMemoryStatDAO vmMemoryStatDAO, Version version, VmStatusListenerRegistrar registrar) {
         super(new BackendID("VM Memory Backend", VmMemoryBackend.class.getName()));
         this.vmMemoryStats = vmMemoryStatDAO;
+        this.registrar = registrar;
         
         setConfigurationValue(BackendsProperties.VENDOR.name(), "Red Hat, Inc.");
         setConfigurationValue(BackendsProperties.DESCRIPTION.name(), "Gathers memory 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 VmMemoryHostListener(vmMemoryStats, attachToNewProcessByDefault());
         } catch (MonitorException me) {
             LOGGER.log(Level.WARNING, "Problems with connecting jvmstat to local machine", me);
         } catch (URISyntaxException use) {
@@ -83,25 +92,17 @@
     @Override
     public boolean activate() {
         if (!started && host != null) {
-            try {
-                host.addHostListener(hostListener);
-                started = true;
-            } catch (MonitorException me) {
-                LOGGER.log(Level.WARNING, "Failed to add host listener", me);
-            }
+            registrar.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, "Failed to remove host listener");
-            }
+        if (started) {
+            registrar.unregister(this);
+            started = false;
         }
         return !started;
     }
@@ -127,6 +128,58 @@
     }
 
     /*
+     * Methods for VmStatusListener
+     */
+    public void vmStatusChanged(Status newStatus, int pid) {
+        switch (newStatus) {
+        case VM_STARTED:
+            /* fall-through */
+        case VM_ACTIVE:
+            handleNewVm(pid);
+            break;
+        case VM_STOPPED:
+            handleStoppedVm(pid);
+            break;
+        default:
+            break;
+        }
+    };
+
+    private void handleNewVm(int pid) {
+        if (attachToNewProcessByDefault()) {
+            try {
+                MonitoredVm vm = host.getMonitoredVm(host.getHostIdentifier().resolve(new VmIdentifier(String.valueOf(pid))));
+                VmMemoryVmListener listener = new VmMemoryVmListener(vmMemoryStats, pid);
+                vm.addVmListener(listener);
+
+                pidToData.put(pid, new Pair<>(vm, listener));
+                LOGGER.finer("Attached VmListener for VM: " + pid);
+            } catch (MonitorException | URISyntaxException e) {
+                LOGGER.log(Level.WARNING, "unable to attach to vm " + pid, e);
+            }
+        } else {
+            LOGGER.log(Level.FINE, "skipping new vm " + pid);
+        }
+    }
+
+    private void handleStoppedVm(int pid) {
+        Pair<MonitoredVm, ? extends VmListener> data = pidToData.remove(pid);
+        // we were not monitoring pid at all, so nothing to do
+        if (data == null) {
+            return;
+        }
+
+        MonitoredVm vm = data.getFirst();
+        VmListener listener = data.getSecond();
+        try {
+            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) {
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListener.java	Tue Jan 22 16:14:48 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +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.memory.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.memory.common.VmMemoryStatDAO;
-
-public class VmMemoryHostListener implements HostListener {
-
-    private static final Logger logger = LoggingUtils.getLogger(VmMemoryHostListener.class);
-
-    private boolean attachNew;
-
-    private final VmMemoryStatDAO vmMemoryStatDAO;
-
-    private Map<Integer, MonitoredVm> monitoredVms  = new HashMap<>();
-    private Map<MonitoredVm, VmMemoryVmListener> registeredListeners  = new ConcurrentHashMap<>();
-    
-    VmMemoryHostListener(VmMemoryStatDAO vmMemoryStatDAO, boolean attachNew) {
-        this.vmMemoryStatDAO = vmMemoryStatDAO;
-        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) {
-                VmMemoryVmListener listener = new VmMemoryVmListener(vmMemoryStatDAO, 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);
-            VmMemoryVmListener 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, VmMemoryVmListener> getRegisteredListeners() {
-        return registeredListeners;
-    }
-
-}
--- a/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListener.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListener.java	Tue Jan 22 16:15:32 2013 +0100
@@ -76,15 +76,12 @@
     @Override
     public void monitorsUpdated(VmEvent event) {
         MonitoredVm vm = event.getMonitoredVm();
-        if (vm == null) {
-            throw new NullPointerException();
-        }
-        
+
         VmMemoryDataExtractor extractor = new VmMemoryDataExtractor(vm);
-        recordMemoryStat(vm, extractor);
+        recordMemoryStat(extractor);
     }
 
-    void recordMemoryStat(MonitoredVm vm, VmMemoryDataExtractor extractor) {
+    void recordMemoryStat(VmMemoryDataExtractor extractor) {
         try {
             long timestamp = System.currentTimeMillis();
             int maxGenerations = (int) extractor.getTotalGcGenerations();
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/ActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/ActivatorTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -93,6 +93,10 @@
         VmMemoryBackend backend = activator.getBackend();
         assertNotNull(backend);
 
+        // core thermostat activates the backend when the backend is detected
+        // do it manually for the test
+        backend.activate();
+
         activator.stop(context);
         
         assertFalse(backend.isActive());
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackendTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryBackendTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -38,20 +38,31 @@
 
 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.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.net.URISyntaxException;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+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 sun.jvmstat.monitor.event.VmListener;
 
+import com.redhat.thermostat.agent.VmStatusListener.Status;
+import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
+import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
@@ -59,32 +70,158 @@
     
     private VmMemoryBackend backend;
     private MonitoredHost host;
+    private VmStatusListenerRegistrar registrar;
+    private VmMemoryStatDAO vmMemoryStatDao;
 
     @Before
     public void setup() throws MonitorException, URISyntaxException {
-        VmMemoryStatDAO vmMemoryStatDao = mock(VmMemoryStatDAO.class);
+        vmMemoryStatDao = mock(VmMemoryStatDAO.class);
         
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
-        backend = new VmMemoryBackend(vmMemoryStatDao, version);
+
+        registrar = mock(VmStatusListenerRegistrar.class);
+
+        backend = new VmMemoryBackend(vmMemoryStatDao, version, registrar);
         
+        HostIdentifier 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);
+
         backend.setHost(host);
     }
 
     @Test
-    public void testStart() throws MonitorException {
-        backend.activate();
-        verify(host).addHostListener(any(HostListener.class));
+    public void testActivate() {
+        assertTrue(backend.activate());
+
+        verify(registrar).register(backend);
         assertTrue(backend.isActive());
     }
 
     @Test
-    public void testStop() throws MonitorException {
+    public void testActivateTwice() {
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        assertTrue(backend.activate());
+        assertTrue(backend.isActive());
+
+        verify(registrar).register(backend);
+    }
+
+    @Test
+    public void testActivateFailsIfHostIsNull() {
+        backend.setHost(null);
+
+        assertFalse(backend.activate());
+    }
+
+    @Test
+    public void testDeactivate() {
         backend.activate();
         backend.deactivate();
-        verify(host).removeHostListener(any(HostListener.class));
+
+        verify(registrar).unregister(backend);
         assertFalse(backend.isActive());
     }
-    
+
+    @Test
+    public void testDeactiveTwice() {
+        assertTrue(backend.activate());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+
+        assertTrue(backend.deactivate());
+        assertFalse(backend.isActive());
+
+        verify(registrar).unregister(backend);
+    }
+
+    @Test
+    public void testNewVmStarted() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        verify(monitoredVm).addVmListener(isA(VmMemoryVmListener.class));
+    }
+
+    @Test
+    public void testErrorInAttachingToNewVm() throws MonitorException, URISyntaxException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+
+        when(host.getMonitoredVm(VM_ID)).thenThrow(new MonitorException());
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+    }
+
+    @Test
+    public void testVmStopped() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        ArgumentCaptor<VmListener> listenerCaptor = ArgumentCaptor.forClass(VmListener.class);
+        verify(monitoredVm).addVmListener(listenerCaptor.capture());
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verify(monitoredVm).removeVmListener(listenerCaptor.getValue());
+        verify(monitoredVm).detach();
+    }
+
+    @Test
+    public void testUnknownVmStoppedIsIgnored() {
+        int VM_PID = 10;
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verifyNoMoreInteractions(host, vmMemoryStatDao);
+    }
+
+    @Test
+    public void testStoppedVmIsDetachedEvenInPresenceOfErrors() throws URISyntaxException, MonitorException {
+        int VM_PID = 10;
+        VmIdentifier VM_ID = new VmIdentifier(String.valueOf(VM_PID));
+        MonitoredVm monitoredVm = mock(MonitoredVm.class);
+
+        when(host.getMonitoredVm(VM_ID)).thenReturn(monitoredVm);
+
+        backend.vmStatusChanged(Status.VM_STARTED, VM_PID);
+
+        ArgumentCaptor<VmListener> listenerCaptor = ArgumentCaptor.forClass(VmListener.class);
+        verify(monitoredVm).addVmListener(listenerCaptor.capture());
+
+        VmListener vmListener = listenerCaptor.getValue();
+        doThrow(new MonitorException("test")).when(monitoredVm).removeVmListener(vmListener);
+
+        backend.vmStatusChanged(Status.VM_STOPPED, VM_PID);
+
+        verify(monitoredVm).detach();
+    }
+
+    @Test
+    public void testOrderValue() {
+        int orderValue = backend.getOrderValue();
+
+        assertTrue(orderValue > Ordered.ORDER_MEMORY_GROUP);
+        assertTrue(orderValue < Ordered.ORDER_NETWORK_GROUP);
+    }
 }
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryDataExtractorTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryDataExtractorTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -44,6 +44,8 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
+
 import sun.jvmstat.monitor.LongMonitor;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
@@ -136,6 +138,18 @@
     }
 
     @Test
+    public void testGenerationCollectorNone() throws MonitorException {
+        final String MONITOR_NAME = "sun.gc.collector.0.name";
+        MonitoredVm vm = mock(MonitoredVm.class);
+        when(vm.findByName(MONITOR_NAME)).thenReturn(null);
+
+        VmMemoryDataExtractor extractor = new VmMemoryDataExtractor(vm);
+        String returned = extractor.getGenerationCollector(0);
+
+        assertEquals(Generation.COLLECTOR_NONE, returned);
+    }
+
+    @Test
     public void testTotalSpaces() throws MonitorException {
         final Long TOTAL_SPACES = 99l;
         final LongMonitor monitor = mock(LongMonitor.class);
--- a/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryHostListenerTest.java	Tue Jan 22 16:14:48 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.memory.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.memory.common.VmMemoryStatDAO;
-
-public class VmMemoryHostListenerTest {
-    
-    private VmMemoryHostListener hostListener;
-    private MonitoredHost host;
-    private MonitoredVm monitoredVm1;
-    private MonitoredVm monitoredVm2;
-
-    @Before
-    public void setup() throws MonitorException, URISyntaxException {
-        VmMemoryStatDAO vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
-        hostListener = new VmMemoryHostListener(vmMemoryStatDAO, 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-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListenerTest.java	Tue Jan 22 16:14:48 2013 +0100
+++ b/vm-memory/agent/src/test/java/com/redhat/thermostat/vm/memory/agent/internal/VmMemoryVmListenerTest.java	Tue Jan 22 16:15:32 2013 +0100
@@ -37,16 +37,21 @@
 package com.redhat.thermostat.vm.memory.agent.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import sun.jvmstat.monitor.Monitor;
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.event.VmEvent;
 
 import com.redhat.thermostat.storage.model.VmMemoryStat;
 import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
@@ -77,19 +82,20 @@
     };
     
     private VmMemoryVmListener vmListener;
-    private MonitoredVm monitoredVm;
     private VmMemoryDataExtractor extractor;
     private VmMemoryStatDAO vmMemoryStatDAO;
+    private MonitoredVm monitoredVm;
     
     @Before
     public void setup() throws MonitorException {
         final int numGens = 2;
         vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
         vmListener = new VmMemoryVmListener(vmMemoryStatDAO, 0);
-        
         monitoredVm = mock(MonitoredVm.class);
         extractor = mock(VmMemoryDataExtractor.class);
-        
+
+        mockTotalGenerations(numGens);
+
         for (int i = 0; i < numGens; i++) {
             mockGenerationName(i);
             mockGenerationCapacity(i);
@@ -106,6 +112,10 @@
         }
     }
 
+    private void mockTotalGenerations(long gens) throws MonitorException {
+        when(extractor.getTotalGcGenerations()).thenReturn(gens);
+    }
+
     private void mockGenerationName(int gen) throws MonitorException {
         when(extractor.getGenerationName(gen)).thenReturn(GEN_NAMES[gen]);
     }
@@ -141,15 +151,43 @@
     private void mockSpaceUsed(int gen, int space) throws MonitorException {
         when(extractor.getSpaceUsed(gen, space)).thenReturn(SPACE_USED[gen][space]);
     }
-    
+
+    @Test
+    public void testDisconnectedIsNoOp() {
+        vmListener.disconnected(null);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO, extractor);
+    }
+
+    @Test
+    public void testMonitorStatusChangeIsNoOp() {
+        vmListener.monitorStatusChanged(null);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO, extractor);
+    }
+
+    @Test
+    public void testMonitorsUpdated() throws MonitorException {
+        Monitor monitor = mock(Monitor.class);
+        when(monitor.getValue()).thenReturn(Long.valueOf(0));
+        when(monitoredVm.findByName(anyString())).thenReturn(monitor);
+        VmEvent monitorUpdateEvent = mock(VmEvent.class);
+        when(monitorUpdateEvent.getMonitoredVm()).thenReturn(monitoredVm);
+
+        vmListener.monitorsUpdated(monitorUpdateEvent);
+
+        verify(vmMemoryStatDAO).putVmMemoryStat(isA(VmMemoryStat.class));
+    }
+
     @Test
     public void testRecordMemoryStat() {
-        vmListener.recordMemoryStat(monitoredVm, extractor);
+        vmListener.recordMemoryStat(extractor);
         ArgumentCaptor<VmMemoryStat> captor = ArgumentCaptor.forClass(VmMemoryStat.class);
         verify(vmMemoryStatDAO).putVmMemoryStat(captor.capture());
         VmMemoryStat memoryStat = captor.getValue();
         
         Generation[] gens = memoryStat.getGenerations();
+        assertEquals(2, gens.length);
         for (int i = 0; i < gens.length; i++) {
             Generation gen = gens[i];
             assertEquals(GEN_NAMES[i], gen.getName());
@@ -167,4 +205,12 @@
             }
         }
     }
+
+    @Test
+    public void testRecordingMemoryInPresenseOfExtrationErrors() throws MonitorException {
+        when(extractor.getTotalGcGenerations()).thenThrow(new MonitorException());
+        vmListener.recordMemoryStat(extractor);
+
+        verifyNoMoreInteractions(vmMemoryStatDAO);
+    }
 }