changeset 1294:cf49a3a699bd

Add context actions to new VMTree review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-October/008596.html reviewed-by: omajid
author Mario Torre <neugens.limasoftware@gmail.com>
date Thu, 31 Oct 2013 16:37:07 +0100
parents 64d12b250c46
children fa405a7ac9c5
files client/core/src/main/java/com/redhat/thermostat/client/ui/HostContextAction.java client/core/src/main/java/com/redhat/thermostat/client/ui/ReferenceContextAction.java client/core/src/main/java/com/redhat/thermostat/client/ui/VMContextAction.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTracker.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ReferenceContextActionProvider.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTracker.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/HostTreeComponentFactory.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextActionController.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextHandler.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTrackerTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTrackerTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextActionControllerTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextHandlerTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/HostTreeControllerTest.java
diffstat 18 files changed, 901 insertions(+), 275 deletions(-) [+]
line wrap: on
line diff
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/HostContextAction.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/HostContextAction.java	Thu Oct 31 16:37:07 2013 +0100
@@ -63,7 +63,7 @@
  * @see VMContextAction
  */
 @ExtensionPoint
-public interface HostContextAction extends ContextAction {
+public interface HostContextAction extends ReferenceContextAction<HostRef> {
 
     /**
      * A user-visible name for this {@code HostContextAction}. This should be
@@ -78,19 +78,5 @@
      */
     @Override
     LocalizedString 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();
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/ReferenceContextAction.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.storage.core.Ref;
+
+/**
+ * A common interface for {@link ContextAction} that can execute commands
+ * based on {@link Ref}erences.
+ */
+public interface ReferenceContextAction<R extends Ref> extends ContextAction {
+
+    /**
+     * Invoked when the user selects this {@code ReferenceAction}.
+     *
+     * @param reference the host on which this {@code ReferenceAction} was
+     * invoked on.
+     */
+    void execute(R reference);
+    
+    /**
+     * The {@link Filter} returned by this method is used to select what
+     * {@link Ref} this {@code ReferenceAction} is applicable to.
+     */
+    Filter<R> getFilter();
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/VMContextAction.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/VMContextAction.java	Thu Oct 31 16:37:07 2013 +0100
@@ -64,7 +64,7 @@
  * @see HostContextAction
  */
 @ExtensionPoint
-public interface VMContextAction extends ContextAction {
+public interface VMContextAction extends ReferenceContextAction<VmRef> {
 
     /**
      * A user-visible name for this {@code VMContextAction}. Should be
@@ -79,19 +79,5 @@
      */
     @Override
     public LocalizedString 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/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Thu Oct 31 16:37:07 2013 +0100
@@ -36,17 +36,13 @@
 
 package com.redhat.thermostat.client.swing.internal;
 
-import java.awt.event.MouseEvent;
-import java.util.List;
-
 import javax.swing.JFrame;
 
 import com.redhat.thermostat.client.core.progress.ProgressNotifier;
 import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
-import com.redhat.thermostat.client.ui.ContextAction;
 import com.redhat.thermostat.client.ui.MenuAction;
-import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.storage.core.Ref;
@@ -60,8 +56,6 @@
         SHOW_CLIENT_CONFIG,
         SHOW_ABOUT_DIALOG,
         SHUTDOWN,
-        SHOW_HOST_VM_CONTEXT_MENU,
-        HOST_VM_CONTEXT_ACTION,
     }
 
     void addActionListener(ActionListener<Action> capture);
@@ -92,21 +86,7 @@
      * non-zero length) and the menu already exists.
      */
     void removeMenu(MenuAction action);
-    
-    /**
-     * 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();
 
     /**
@@ -114,5 +94,11 @@
      * object tracked by this UI Client.
      */
     HostTreeController getHostTreeController();
+
+    /**
+     * Returns the {@link ContextActionController} that handles the context
+     * actions in the UI Client.
+     */
+    ContextActionController getContextActionController();
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Thu Oct 31 16:37:07 2013 +0100
@@ -56,7 +56,6 @@
 import java.util.List;
 
 import javax.swing.BorderFactory;
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JFrame;
 import javax.swing.JMenu;
 import javax.swing.JMenuBar;
@@ -74,7 +73,6 @@
 import com.redhat.thermostat.client.swing.MenuHelper;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.OverlayPanel;
-import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
 import com.redhat.thermostat.client.swing.internal.accordion.Accordion;
 import com.redhat.thermostat.client.swing.internal.components.ThermostatGlassPane;
 import com.redhat.thermostat.client.swing.internal.components.ThermostatGlassPaneLayout;
@@ -86,9 +84,9 @@
 import com.redhat.thermostat.client.swing.internal.sidepane.ThermostatSidePanel;
 import com.redhat.thermostat.client.swing.internal.splitpane.ThermostatSplitPane;
 import com.redhat.thermostat.client.swing.internal.vmlist.HostTreeComponentFactory;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorManager;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
-import com.redhat.thermostat.client.ui.ContextAction;
 import com.redhat.thermostat.client.ui.MenuAction;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
@@ -115,13 +113,13 @@
 
     private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
 
-    private ThermostatPopupMenu contextMenu;
     private StatusBar statusBar;
     
     private ThermostatSidePanel navigationPanel;
     private Accordion<HostRef, VmRef> hostTree;
     
     private HostTreeController hostTreeController;
+    private ContextActionController contextActionController;
     
     public MainWindow() {
         super();
@@ -241,18 +239,6 @@
         editMenu.add(configureClientMenuItem);
 
         editMenu.addSeparator();
-        
-        // FIXME: re-add this when ready
-//        JMenuItem historyModeMenuItem = new JCheckBoxMenuItem(translator.localize(LocaleResources.MENU_EDIT_ENABLE_HISTORY_MODE).getContents());
-//        historyModeMenuItem.setName("historyModeSwitch");
-//        historyModeMenuItem.setSelected(false);
-//        historyModeMenuItem.addActionListener(new java.awt.event.ActionListener() {
-//            @Override
-//            public void actionPerformed(java.awt.event.ActionEvent e) {
-//                fireViewAction(Action.SWITCH_HISTORY_MODE);
-//            }
-//        });
-//        editMenu.add(historyModeMenuItem);
 
         JMenu viewMenu = new JMenu(translator.localize(LocaleResources.MENU_VIEW).getContents());
         mainMenuBar.add(viewMenu);
@@ -294,8 +280,10 @@
         splitPane.setLeftComponent(navigationPanel);
         
         DecoratorManager decoratorManager = new DecoratorManager();
+        contextActionController = new ContextActionController();
         
-        HostTreeComponentFactory hostFactory = new HostTreeComponentFactory(decoratorManager);
+        HostTreeComponentFactory hostFactory =
+                new HostTreeComponentFactory(decoratorManager, contextActionController);
         hostTree = new Accordion<>(hostFactory);
         hostTreeController = new HostTreeController(hostTree, decoratorManager,
                                                     hostFactory);
@@ -352,51 +340,6 @@
         });
     }
     
-    // TODO
-//    private void registerContextActionListener(JTree agentVmTree2) {
-//        contextMenu = new ThermostatPopupMenu();
-//        agentVmTree2.addMouseListener(new MouseAdapter() {
-//            @Override
-//            public void mousePressed(MouseEvent e) {
-//                if (e.isPopupTrigger()) {
-//                    Ref ref = getSelectedHostOrVm();
-//                    fireViewAction(Action.SHOW_HOST_VM_CONTEXT_MENU, e);
-//                }
-//            }
-//        });
-//    }
-
-    @Override
-    public void showContextActions(final List<ContextAction> actions, final MouseEvent e) {
-        SwingUtilities.invokeLater(new Runnable() {
-
-            @Override
-            public void run() {
-                contextMenu.removeAll();
-
-                for (final ContextAction action: actions) {
-                    JMenuItem contextAction = new JMenuItem();
-                    contextAction.setText(action.getName().getContents());
-                    contextAction.setToolTipText(action.getDescription().getContents());
-
-                    contextAction.addActionListener(new java.awt.event.ActionListener() {
-                        @Override
-                        public void actionPerformed(java.awt.event.ActionEvent e) {
-                            fireViewAction(Action.HOST_VM_CONTEXT_ACTION, action);
-                        }
-                    });
-
-                    // the component name is for unit tests only
-                    contextAction.setName(action.getName().getContents());
-
-                    contextMenu.add(contextAction);
-                }
-
-                contextMenu.show((Component)e.getSource(), e.getX(), e.getY());
-            }
-        });
-    }
-    
     private JPanel createDetailsPanel() {
         JPanel result = new JPanel(new BorderLayout());
         result.add(contentArea, BorderLayout.CENTER);
@@ -444,11 +387,7 @@
     private void fireViewAction(Action action) {
         actionNotifier.fireAction(action);
     }
-    
-    private void fireViewAction(Action action, Object payload) {
-        actionNotifier.fireAction(action, payload);
-    }
-    
+        
     @SuppressWarnings("unused") // Used for debugging but not in production code.
     private static void printTree(PrintStream out, TreeNode node, int depth) {
         out.println(StringUtils.repeat("  ", depth) + node.toString());
@@ -528,5 +467,10 @@
     public HostTreeController getHostTreeController() {
         return hostTreeController;
     }
+
+    @Override
+    public ContextActionController getContextActionController() {
+        return contextActionController;
+    }
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Thu Oct 31 16:37:07 2013 +0100
@@ -56,10 +56,11 @@
 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.swing.internal.MainView.Action;
 import com.redhat.thermostat.client.swing.internal.osgi.HostContextActionServiceTracker;
 import com.redhat.thermostat.client.swing.internal.osgi.InformationServiceTracker;
 import com.redhat.thermostat.client.swing.internal.osgi.VMContextActionServiceTracker;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextHandler;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorProviderExtensionListener;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.FilterManager;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
@@ -343,12 +344,6 @@
                 case SHOW_ABOUT_DIALOG:
                     showAboutDialog();
                     break;
-                case SHOW_HOST_VM_CONTEXT_MENU:
-                    showContextMenu(evt);
-                    break;
-                case HOST_VM_CONTEXT_ACTION:
-                    handleVMHooks(evt);
-                    break;
                 case SHUTDOWN:
                     // Main will call shutdownApplication
                     shutdown.countDown();
@@ -400,8 +395,20 @@
 
         vmInfoRegistry.addActionListener(vmInfoRegistryListener);
         vmInfoRegistry.start();
+
+        setUpActionControllers();
     }
 
+    private void setUpActionControllers() {
+        ContextActionController contextController =
+                view.getContextActionController();
+        ContextHandler handler =
+                new ContextHandler(hostContextActionTracker,
+                                   vmContextActionTracker);
+        contextController.addContextActionListener(handler);
+        handler.addContextHandlerActionListener(contextController);
+    }
+    
     private void registerProgressNotificator(BundleContext context) {
         ProgressNotifier notifier = view.getNotifier();
         context.registerService(ProgressNotifier.class, notifier, null);
@@ -431,51 +438,6 @@
         vmInfoRegistry.stop();
     }
 
-    private void showContextMenu(ActionEvent<Action> evt) {
-        // TODO
-//        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");
-//
-//            for (HostContextAction action : hostContextActionTracker.getHostContextActions()) {
-//                if (action.getFilter().matches(vm)) {
-//                    toShow.add(action);
-//                }
-//            }
-//        } else if (ref instanceof VmRef) {
-//            VmRef vm = (VmRef) ref;
-//
-//            logger.log(Level.INFO, "registering applicable VMContextActions actions to show");
-//
-//            for (VMContextAction action : vmContextActionTracker.getVmContextActions()) {
-//                if (action.getFilter().matches(vm)) {
-//                    toShow.add(action);
-//                }
-//            }
-//        }
-//
-//        view.showContextActions(toShow, (MouseEvent) evt.getPayload());
-    }
-
-    private void handleVMHooks(ActionEvent<MainView.Action> event) {
-//        Object payload = event.getPayload();
-//        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, "error invocating context action", error);
-//        }
-    }
-
     @Override
     public void showMainMainWindow() {
         try {
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTracker.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTracker.java	Thu Oct 31 16:37:07 2013 +0100
@@ -38,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -46,14 +47,14 @@
 import com.redhat.thermostat.client.ui.HostContextAction;
 
 @SuppressWarnings("rawtypes")
-public class HostContextActionServiceTracker extends ServiceTracker {
+public class HostContextActionServiceTracker extends ServiceTracker implements ReferenceContextActionProvider {
     
     private List<HostContextAction> hostContextActions;
 
     @SuppressWarnings("unchecked")
     public HostContextActionServiceTracker(BundleContext context) {
         super(context, HostContextAction.class.getName(), null);
-        this.hostContextActions = new ArrayList<>();
+        this.hostContextActions = new CopyOnWriteArrayList<>();
     }
 
     @Override
@@ -70,10 +71,10 @@
         hostContextActions.remove((HostContextAction)service);
         super.removedService(reference, service);
     }
-    
-    public List<HostContextAction> getHostContextActions() {
+
+    @Override
+    public List<HostContextAction> getActions() {
         return new ArrayList<>(hostContextActions);
     }
-    
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ReferenceContextActionProvider.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.osgi;
+
+import java.util.List;
+
+import com.redhat.thermostat.client.ui.ReferenceContextAction;
+
+public interface ReferenceContextActionProvider {
+    
+    public List<? extends ReferenceContextAction> getActions();
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTracker.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTracker.java	Thu Oct 31 16:37:07 2013 +0100
@@ -38,6 +38,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -46,14 +47,14 @@
 import com.redhat.thermostat.client.ui.VMContextAction;
 
 @SuppressWarnings("rawtypes")
-public class VMContextActionServiceTracker extends ServiceTracker {
+public class VMContextActionServiceTracker extends ServiceTracker implements ReferenceContextActionProvider {
 
     private List<VMContextAction> vmContextActions;
 
     @SuppressWarnings("unchecked")
     public VMContextActionServiceTracker(BundleContext context) {
         super(context, VMContextAction.class.getName(), null);
-        this.vmContextActions = new ArrayList<>();
+        this.vmContextActions = new CopyOnWriteArrayList<>();
     }
 
     @Override
@@ -71,7 +72,8 @@
         super.removedService(reference, service);
     }
     
-    public List<VMContextAction> getVmContextActions() {
+    @Override
+    public List<VMContextAction> getActions() {
         return new ArrayList<>(vmContextActions);
     }
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/HostTreeComponentFactory.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/HostTreeComponentFactory.java	Thu Oct 31 16:37:07 2013 +0100
@@ -42,6 +42,7 @@
 import com.redhat.thermostat.client.swing.internal.accordion.AccordionComponent;
 import com.redhat.thermostat.client.swing.internal.accordion.AccordionComponentFactory;
 import com.redhat.thermostat.client.swing.internal.accordion.TitledPane;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorManager;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.VmRef;
@@ -52,9 +53,14 @@
     private Map<HostRef, ReferenceTitle> headers;
     
     private DecoratorManager decoratorManager;
+    private ContextActionController contextActionController;
     
-    public HostTreeComponentFactory(DecoratorManager decoratorManager) {
+    public HostTreeComponentFactory(DecoratorManager decoratorManager,
+                                    ContextActionController contextActionController)
+    {
         this.decoratorManager = decoratorManager;
+        this.contextActionController = contextActionController;
+        
         components = new HashMap<>();
         headers = new HashMap<>();
     }
@@ -63,6 +69,7 @@
     public TitledPane createHeader(HostRef header) {
         ReferenceTitle pane = new ReferenceTitle(header);
         decoratorManager.registerAndSetIcon(pane);
+        contextActionController.register(pane, pane);
         headers.put(header, pane);
 
         return pane;
@@ -72,6 +79,7 @@
     public AccordionComponent createComponent(HostRef header, VmRef component) {
         ReferenceComponent refComponent = new ReferenceComponent(component);
         decoratorManager.registerAndSetIcon(refComponent);
+        contextActionController.register(refComponent, refComponent);
         components.put(component, refComponent);
 
         return refComponent;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextActionController.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.vmlist.controller;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.client.swing.internal.accordion.AccordionComponent;
+import com.redhat.thermostat.client.swing.internal.vmlist.ReferenceProvider;
+import com.redhat.thermostat.client.ui.ReferenceContextAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Ref;
+
+public class ContextActionController implements ActionListener<ContextHandler.ContextHandlerAction>{
+
+    public enum ContextAction {
+        SHOW_CONTEXT_MENU,
+    }
+
+    public static class Payload {
+        public AccordionComponent component;
+        public int x;
+        public int y;
+        public Ref ref;
+    }
+    
+    private static final Logger logger = LoggingUtils.getLogger(ContextActionController.class);
+    
+    private ActionNotifier<ContextAction> notifier;
+    
+    public ContextActionController() {
+        notifier = new ActionNotifier<>(this);
+    }
+    
+    public void register(final AccordionComponent pane,
+                         final ReferenceProvider provider)
+    {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                pane.getUiComponent().addMouseListener(new MouseAdapter() {
+                    @Override
+                    public void mousePressed(MouseEvent e) {                        
+                        if (e.isPopupTrigger()) {
+                            Payload payload = new Payload();
+                            payload.ref = provider.getReference();
+                            payload.component = pane;
+                            payload.x = e.getX();
+                            payload.y = e.getY();
+                            notifier.fireAction(ContextAction.SHOW_CONTEXT_MENU, payload);
+                      }
+                    }
+                });
+            }
+        });
+    }
+    
+    public void addContextActionListener(ActionListener<ContextAction> l) {
+        notifier.addActionListener(l);
+    }
+    
+    public void removeContextActionListener(ActionListener<ContextAction> l) {
+        notifier.removeActionListener(l);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public void actionPerformed(ActionEvent<ContextHandler.ContextHandlerAction> event) {
+        ContextHandler.Payload payload = (ContextHandler.Payload) event.getPayload();
+        try {
+            ReferenceContextAction action = payload.action;
+            action.execute(payload.reference);
+        } catch (Throwable error) {
+            logger.log(Level.SEVERE, "error invocating context action", error);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextHandler.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.vmlist.controller;
+
+import java.util.List;
+
+import javax.swing.JMenuItem;
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
+import com.redhat.thermostat.client.swing.internal.osgi.HostContextActionServiceTracker;
+import com.redhat.thermostat.client.swing.internal.osgi.VMContextActionServiceTracker;
+import com.redhat.thermostat.client.ui.ReferenceContextAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.Ref;
+
+/**
+ *
+ */
+public class ContextHandler implements ActionListener<ContextActionController.ContextAction>{
+
+    public enum ContextHandlerAction {
+        ACTION_PERFORMED,
+    }
+
+    public static class Payload<R extends Ref> {
+        ReferenceContextAction<R> action;
+        Ref reference;
+    }
+    
+    private ActionNotifier<ContextHandlerAction> notifier;
+    
+    private ThermostatPopupMenu contextMenu;
+    
+    private HostContextActionServiceTracker hostContextActionTracker;
+    private VMContextActionServiceTracker vmContextActionTracker;
+    
+    public ContextHandler(HostContextActionServiceTracker hostContextActionTracker,
+                          VMContextActionServiceTracker vmContextActionTracker)
+    {
+        this.hostContextActionTracker = hostContextActionTracker;
+        this.vmContextActionTracker = vmContextActionTracker;
+        notifier = new ActionNotifier<>(this);
+    }
+
+    @Override
+    public void actionPerformed(final ActionEvent<ContextActionController.ContextAction> actionEvent) {
+        SwingUtilities.invokeLater(new Runnable() {
+            
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            @Override
+            public void run() {
+                if (contextMenu == null) {
+                    contextMenu = createContextPopMenu();
+                }
+                
+                final ContextActionController.Payload payload =
+                        (ContextActionController.Payload) actionEvent.getPayload();
+                
+                List<? extends ReferenceContextAction> actions = null;
+                if (payload.ref instanceof HostRef) {
+                    actions = hostContextActionTracker.getActions();
+                } else {
+                    actions = vmContextActionTracker.getActions();
+                }
+
+                contextMenu.removeAll();
+                
+                boolean showPopup = false;
+                for (final ReferenceContextAction action : actions) {
+                    
+                    if (!action.getFilter().matches(payload.ref))  {
+                        continue;
+                    }
+                    
+                    showPopup = true;
+                    
+                    JMenuItem contextAction = createContextMenuItem();
+                    contextAction.setText(action.getName().getContents());
+                    contextAction.setToolTipText(action.getDescription().getContents());
+                    contextAction.addActionListener(new java.awt.event.ActionListener() {
+                        @Override
+                        public void actionPerformed(java.awt.event.ActionEvent e) {
+                            Payload actionPayload = new Payload();
+                            actionPayload.reference = payload.ref;
+                            actionPayload.action = action;
+                            
+                            notifier.fireAction(ContextHandlerAction.ACTION_PERFORMED, actionPayload);
+                        }
+                    });
+
+                    // the component name is for unit tests only
+                    contextAction.setName(action.getName().getContents());
+
+                    contextMenu.add(contextAction);
+                }
+                
+                if (showPopup) {
+                    contextMenu.show(payload.component.getUiComponent(), payload.x, payload.y);
+                }
+            }
+        });
+    }
+    
+    // allow us to inject a mock for testing 
+    ThermostatPopupMenu createContextPopMenu() {
+        return new ThermostatPopupMenu();
+    }
+    
+    // allow us to inject a mock for testing 
+    JMenuItem createContextMenuItem() {
+        return new JMenuItem();
+    }
+    
+    public void addContextHandlerActionListener(ActionListener<ContextHandlerAction> l) {
+        notifier.addActionListener(l);
+    }
+    
+    public void removeContextHandlerActionListener(ActionListener<ContextHandlerAction> l) {
+        notifier.removeActionListener(l);
+    }
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -37,14 +37,14 @@
 package com.redhat.thermostat.client.swing.internal;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.any;
 
 import java.util.concurrent.CountDownLatch;
 
@@ -66,6 +66,8 @@
 import com.redhat.thermostat.client.core.views.SummaryViewProvider;
 import com.redhat.thermostat.client.core.views.VmInformationView;
 import com.redhat.thermostat.client.core.views.VmInformationViewProvider;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextHandler;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorProviderExtensionListener;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
 import com.redhat.thermostat.client.ui.HostContextAction;
@@ -127,6 +129,8 @@
     private DecoratorProviderExtensionListener<HostRef> hostDecorators;
     private DecoratorProviderExtensionListener<VmRef> vmDecorators;
     
+    private ContextActionController contextController;
+    
     @BeforeClass
     public static void setUpOnce() {
         // TODO remove when controller uses mocked objects rather than real swing objects
@@ -137,7 +141,7 @@
     @Before
     public void setUp() throws Exception {
         context = new StubBundleContext();
-        
+                
         // Setup timers
         mainWindowTimer = mock(Timer.class);
         Timer otherTimer = mock(Timer.class); // FIXME needed for SummaryView; remove later
@@ -189,6 +193,9 @@
         ArgumentCaptor<ActionListener> grabListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(grabListener.capture());
         
+        contextController = mock(ContextActionController.class);
+        when(view.getContextActionController()).thenReturn(contextController);
+        
         hostDecorators = mock(DecoratorProviderExtensionListener.class);
         vmDecorators = mock(DecoratorProviderExtensionListener.class);
         
@@ -307,6 +314,8 @@
         
         assertEquals(hostDecorators, l1);
         assertEquals(vmDecorators, l2);
+        
+        verify(contextController).addContextActionListener(any(ContextHandler.class));
     }
     
     @Test
@@ -314,30 +323,7 @@
         controller.showMainMainWindow();
         verify(view).showMainWindow();
     }
-    
-//    @Test
-//    public void verifyUpdateHostsVMsLoadsCorrectVMWithFilter() {
-//
-//        VmRef ref1 = mock(VmRef.class);
-//        when(ref1.getStringID()).thenReturn("test1");
-//        when(ref1.getName()).thenReturn("test1");
-//        
-//        VmRef ref2 = mock(VmRef.class);
-//        when(ref2.getStringID()).thenReturn("test2");
-//        when(ref2.getName()).thenReturn("test2");
-//        
-//        controller.setHostVmTreeFilter("test1");
-//                
-//        Filter<VmRef> filter = controller.getVmFilter();
-//        assertTrue(filter.matches(ref1));
-//        assertFalse(filter.matches(ref2));
-//    }
-    
-//    private void assertEqualCollection(Collection<?> expected, Collection<?> actual) {
-//        assertEquals(expected.size(), actual.size());
-//        assertTrue(expected.containsAll(actual));
-//    }
-//
+
 //    @Test
 //    @Bug(id="954",
 //         summary="Thermostat GUI client should remember my last panel selected",
@@ -447,69 +433,6 @@
 //
 //        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("foo", "123", 0, 1, 2, null, null, null, null, null, null, null, null, null, null, null, -1, null);
-//        when(mockVmsDAO.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
-//
-//        VmRef ref = mock(VmRef.class);
-//        when(view.getSelectedHostOrVm()).thenReturn(ref);
-//
-//        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(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.HOST_VM_CONTEXT_ACTION);
-//        event.setPayload(vmContextAction1);
-//        l.actionPerformed(event);
-//        
-//        verify(vmContextAction1, times(1)).execute(any(VmRef.class));
-//        verify(vmContextAction2, times(0)).execute(any(VmRef.class));
-//    }
 
     @Test
     public void verifyMenuItems() {
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTrackerTest.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/HostContextActionServiceTrackerTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -58,13 +58,13 @@
         HostContextActionServiceTracker tracker = new HostContextActionServiceTracker(bundleContext);
         tracker.open();
         
-        assertTrue(tracker.getHostContextActions().contains(hostAction));
+        assertTrue(tracker.getActions().contains(hostAction));
 
         registration.unregister();
 
         tracker.close();
 
-        assertFalse(tracker.getHostContextActions().contains(hostAction));
+        assertFalse(tracker.getActions().contains(hostAction));
     }
 
 }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTrackerTest.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/VMContextActionServiceTrackerTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -58,13 +58,13 @@
         VMContextActionServiceTracker tracker = new VMContextActionServiceTracker(bundleContext);
         tracker.open();
         
-        assertTrue(tracker.getVmContextActions().contains(vmAction));
+        assertTrue(tracker.getActions().contains(vmAction));
 
         registration.unregister();
 
         tracker.close();
 
-        assertFalse(tracker.getVmContextActions().contains(vmAction));
+        assertFalse(tracker.getActions().contains(vmAction));
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextActionControllerTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.vmlist.controller;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.JComponent;
+import javax.swing.RepaintManager;
+import javax.swing.SwingUtilities;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.swing.internal.accordion.AccordionComponent;
+import com.redhat.thermostat.client.swing.internal.vmlist.ReferenceProvider;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController.ContextAction;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextActionController.Payload;
+import com.redhat.thermostat.client.ui.ReferenceContextAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.storage.core.HostRef;
+
+public class ContextActionControllerTest {
+
+    private JComponent jcomponent;
+    private AccordionComponent component;
+    private ReferenceProvider provider;
+    
+    @BeforeClass
+    public static void setUpOnce() {
+        // This is needed because some other test may have installed the
+        // EDT violation checker repaint manager.
+        RepaintManager.setCurrentManager(new RepaintManager());
+    }
+    
+    @Before
+    public void setUp() {
+        component = mock(AccordionComponent.class);
+        provider = mock(ReferenceProvider.class);
+        jcomponent = mock(JComponent.class);
+    }
+    
+    private void waitForSwing() {
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    // just wait :)
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+    
+    @Test
+    public void testRegister() {
+        
+        ArgumentCaptor<MouseListener> captor = ArgumentCaptor.forClass(MouseListener.class);
+        MouseEvent e = mock(MouseEvent.class);
+
+        when(e.getX()).thenReturn(5);
+        when(e.getY()).thenReturn(10);
+        
+        HostRef ref = new HostRef("0", "0");
+        
+        when(e.isPopupTrigger()).thenReturn(true);
+        when(component.getUiComponent()).thenReturn(jcomponent);
+        when(provider.getReference()).thenReturn(ref);
+        doNothing().when(jcomponent).addMouseListener(captor.capture());
+        
+        final ContextActionController.Payload[] payload = new ContextActionController.Payload[1]; 
+        ActionListener<ContextActionController.ContextAction> l =
+                new ActionListener<ContextActionController.ContextAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<ContextAction> actionEvent) {
+                payload[0] = (Payload) actionEvent.getPayload();
+            }
+        };
+        
+        ContextActionController controller = new ContextActionController();
+        controller.register(component, provider);
+        controller.addContextActionListener(l);
+        
+        waitForSwing();
+        
+        MouseListener mouseListener = captor.getValue();
+        mouseListener.mousePressed(e);
+        
+        assertNotNull(payload[0]);
+        assertEquals(ref, payload[0].ref);
+        assertEquals(component, payload[0].component);
+        assertEquals(5, payload[0].x);
+        assertEquals(10, payload[0].y);
+    }
+    
+    @Test
+    public void testExecute() {
+        ContextActionController controller = new ContextActionController();
+        
+        ContextHandler.Payload payload = new ContextHandler.Payload();
+        payload.action = mock(ReferenceContextAction.class);
+        payload.reference = new HostRef("0", "0");
+
+        ActionEvent<ContextHandler.ContextHandlerAction> event =
+                new ActionEvent<ContextHandler.ContextHandlerAction>(this,
+                        ContextHandler.ContextHandlerAction.ACTION_PERFORMED);
+        event.setPayload(payload);
+        
+        controller.actionPerformed(event);
+        
+        verify(payload.action).execute(payload.reference);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/ContextHandlerTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ * 
+ * This file is part of Thermostat.
+ * 
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ * 
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ * 
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ * 
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.internal.vmlist.controller;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.doNothing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.awt.Component;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JComponent;
+import javax.swing.JMenuItem;
+import javax.swing.RepaintManager;
+import javax.swing.SwingUtilities;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
+import com.redhat.thermostat.client.swing.internal.accordion.AccordionComponent;
+import com.redhat.thermostat.client.swing.internal.osgi.HostContextActionServiceTracker;
+import com.redhat.thermostat.client.swing.internal.osgi.VMContextActionServiceTracker;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextHandler.ContextHandlerAction;
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.ContextHandler.Payload;
+import com.redhat.thermostat.client.ui.HostContextAction;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.storage.core.HostRef;
+
+/**
+ *
+ */
+public class ContextHandlerTest {
+
+    private HostContextActionServiceTracker hostContextActionTracker;
+    private VMContextActionServiceTracker vmContextActionTracker;
+    private ActionEvent actionEvent;
+    
+    @BeforeClass
+    public static void setUpOnce() {
+        // This is needed because some other test may have installed the
+        // EDT violation checker repaint manager.
+        RepaintManager.setCurrentManager(new RepaintManager());
+    }
+    
+    private void waitForSwing() {
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    // just wait :)
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+    
+    private class TestActionListener implements com.redhat.thermostat.common.ActionListener<ContextHandlerAction> {
+        
+        ActionEvent<ContextHandlerAction> actionEvent;
+        
+        @Override
+        public void actionPerformed(ActionEvent<ContextHandlerAction> actionEvent) {
+            this.actionEvent = actionEvent;
+        }
+    }
+    
+    @Before
+    public void setUp() {
+        hostContextActionTracker = mock(HostContextActionServiceTracker.class);
+        vmContextActionTracker = mock(VMContextActionServiceTracker.class);
+        actionEvent = mock(ActionEvent.class);
+    }
+    
+    @Test
+    public void testActions() {
+        
+        ArgumentCaptor<ActionListener> captor0 = ArgumentCaptor.forClass(ActionListener.class);
+        ArgumentCaptor<ActionListener> captor1 = ArgumentCaptor.forClass(ActionListener.class);
+        
+        final int[] invocations = new int[2]; 
+        
+        final ThermostatPopupMenu popup = mock(ThermostatPopupMenu.class);
+        final JMenuItem menuItem0 = mock(JMenuItem.class);
+        final JMenuItem menuItem1 = mock(JMenuItem.class);
+
+        doNothing().when(menuItem0).addActionListener(captor0.capture());
+        doNothing().when(menuItem1).addActionListener(captor1.capture());
+        
+        final JMenuItem[] items = new JMenuItem[2]; 
+        items[0] = menuItem0;
+        items[1] = menuItem1;
+        
+        ContextActionController.Payload payload = new ContextActionController.Payload();
+        when(actionEvent.getPayload()).thenReturn(payload);
+        
+        HostRef host0 = new HostRef("0", "0");
+        payload.ref = host0;
+        
+        JComponent accordionComponent = mock(JComponent.class);
+        AccordionComponent accordion = mock(AccordionComponent.class);
+        when(accordion.getUiComponent()).thenReturn(accordionComponent);
+        payload.component = accordion;
+        payload.x = 10;
+        payload.y = 10;
+
+        TestActionListener testListener = new TestActionListener();
+        
+        ContextHandler handler = new ContextHandler(hostContextActionTracker, vmContextActionTracker) {
+            @Override
+            ThermostatPopupMenu createContextPopMenu() {
+                invocations[0]++;
+                return popup;
+            }
+            @Override
+            JMenuItem createContextMenuItem() {
+                return items[invocations[1]++ % 2];
+            }    
+        };
+        handler.addContextHandlerActionListener(testListener);
+        
+        // *** test no action
+        
+        handler.actionPerformed(actionEvent);
+        
+        waitForSwing();
+        
+        assertEquals(1, invocations[0]);
+        assertEquals(0, invocations[1]);
+        
+        verify(hostContextActionTracker).getActions();
+        verify(vmContextActionTracker, times(0)).getActions();
+        
+        // verify the popup is built from scratch
+        verify(popup).removeAll();
+
+        verify(popup, times(0)).show(any(Component.class), anyInt(), anyInt());
+        
+        // *** test two actions, no filter
+        
+        // no reason to change the event, but add actions
+        Filter<HostRef> hostFilter = mock(Filter.class);
+        when(hostFilter.matches(host0)).thenReturn(true).thenReturn(true);
+        
+        LocalizedString name0 = new LocalizedString("actionName0");
+        LocalizedString des0 = new LocalizedString("actionDesc0");
+
+        HostContextAction hostAction0 = mock(HostContextAction.class);
+        when(hostAction0.getFilter()).thenReturn(hostFilter);
+        when(hostAction0.getName()).thenReturn(name0);
+        when(hostAction0.getDescription()).thenReturn(des0);
+
+        LocalizedString name1 = new LocalizedString("actionName1");
+        LocalizedString des1 = new LocalizedString("actionDesc1");
+        
+        HostContextAction hostAction1 = mock(HostContextAction.class);
+        when(hostAction1.getFilter()).thenReturn(hostFilter);
+        when(hostAction1.getName()).thenReturn(name1);
+        when(hostAction1.getDescription()).thenReturn(des1);
+        
+        List<HostContextAction> hostActions = new ArrayList<>();
+        hostActions.add(hostAction0);
+        hostActions.add(hostAction1);
+        
+        when(hostContextActionTracker.getActions()).thenReturn(hostActions);
+        
+        handler.actionPerformed(actionEvent);
+        waitForSwing();
+
+        assertEquals(1, invocations[0]);
+        assertEquals(2, invocations[1]);
+     
+        verify(popup, times(2)).removeAll();
+
+        verify(popup).add(menuItem0);
+        verify(popup).add(menuItem1);
+
+        verify(popup, times(1)).show(accordionComponent, 10, 10);
+
+        verifyNoMoreInteractions(popup);
+        
+        verify(menuItem0).setText(name0.getContents());
+        verify(menuItem1).setText(name1.getContents());
+        
+        verify(menuItem0).setToolTipText(des0.getContents());
+        verify(menuItem1).setToolTipText(des1.getContents());
+        
+        // *** check that we are notified for the correct action
+        
+        ActionListener l0 = captor0.getValue();
+        ActionListener l1 = captor1.getValue();
+        
+        java.awt.event.ActionEvent fakeEvent = mock(java.awt.event.ActionEvent.class);
+        l0.actionPerformed(fakeEvent);
+        
+        assertNotNull(testListener.actionEvent);
+        
+        Payload eventPayload = (Payload) testListener.actionEvent.getPayload();
+        assertNotNull(eventPayload);
+        assertEquals(host0, eventPayload.reference);
+        assertEquals(hostAction0, eventPayload.action);
+
+        l1.actionPerformed(fakeEvent);
+        assertNotNull(testListener.actionEvent);
+
+        eventPayload = (Payload) testListener.actionEvent.getPayload();
+        assertNotNull(eventPayload);
+        assertEquals(host0, eventPayload.reference);
+        assertEquals(hostAction1, eventPayload.action);
+        
+        // *** now test again with one filtering
+        
+        invocations[1] = 0;
+        
+        when(hostFilter.matches(host0)).thenReturn(false).thenReturn(true);
+        handler.actionPerformed(actionEvent);
+        waitForSwing();
+        
+        assertEquals(1, invocations[0]);
+        assertEquals(1, invocations[1]);
+        
+        // TODO: the numbers here take into account the previous
+        // invocation this should really be a separate test, but there's too
+        // much stubbing to duplicate, consider refactoring the test
+        
+        verify(popup, times(3)).removeAll();
+
+        verify(popup, times(2)).add(menuItem0);
+        verify(popup, times(2)).show(accordionComponent, 10, 10);
+
+        verifyNoMoreInteractions(popup);
+        
+        verify(menuItem0).setText(name0.getContents());
+        verify(menuItem0).setToolTipText(des0.getContents());
+    }    
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/HostTreeControllerTest.java	Wed Oct 30 14:32:31 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/HostTreeControllerTest.java	Thu Oct 31 16:37:07 2013 +0100
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
@@ -59,7 +60,6 @@
 import com.redhat.thermostat.client.swing.internal.vmlist.HostTreeComponentFactory;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.test.Bug;
 
 public class HostTreeControllerTest {