changeset 926:4f3b798807f3

Introduce HostContextAction Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004957.html
author Omair Majid <omajid@redhat.com>
date Mon, 21 Jan 2013 17:17:28 -0500
parents 259c60624174
children 279aeb153688
files client/core/src/main/java/com/redhat/thermostat/client/osgi/service/HostContextAction.java client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.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/UiFacadeFactoryImpl.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/ThermostatActivator.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/MainWindowTest.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/ThermostatActivatorTest.java
diffstat 13 files changed, 452 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/HostContextAction.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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/VMContextAction.java	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Mon Jan 21 17:17:28 2013 -0500
@@ -59,6 +59,8 @@
  * 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 {
@@ -78,10 +80,10 @@
     public String getDescription();
 
     /**
-     * Invoked when the user selects this context item
+     * Invoked when the user selects this {@code VMContextAction}.
      *
-     * @param reference specifies the vm that this context action was invoked
-     * on.
+     * @param reference specifies the vm that this {@code VMContextAction} was
+     * invoked on.
      */
     void execute(VmRef reference);
 
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/UiFacadeFactory.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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/internal/MainView.java	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainView.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindow.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/UiFacadeFactoryImpl.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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
--- /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	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivator.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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/MainWindowControllerImplTest.java	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowTest.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 17:17:28 2013 -0500
@@ -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	Mon Jan 21 13:48:50 2013 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/osgi/ThermostatActivatorTest.java	Mon Jan 21 17:17:28 2013 -0500
@@ -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
     }
 }