changeset 339:cd786147af62

Merge
author Roman Kennke <rkennke@redhat.com>
date Wed, 30 May 2012 20:16:19 +0200
parents eae8d583ebd4 (current diff) 9368bea3c19c (diff)
children d4850c83bbc1
files tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java
diffstat 37 files changed, 910 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/agent/src/main/java/com/redhat/thermostat/agent/Activator.java	Wed May 30 20:14:55 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/Activator.java	Wed May 30 20:16:19 2012 +0200
@@ -37,30 +37,26 @@
 package com.redhat.thermostat.agent;
 
 import java.util.Arrays;
-import java.util.Collection;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
 public class Activator implements BundleActivator {
 
-    private Collection<ServiceRegistration> agentCmd;
+    private CommandRegistry reg;
 
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
-        agentCmd = reg.registerCommands(Arrays.asList(new AgentApplication()));
+        reg = new CommandRegistryImpl(context);
+        reg.registerCommands(Arrays.asList(new AgentApplication()));
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        for (ServiceRegistration reg : agentCmd) {
-            reg.unregister();
-        }
+        reg.unregisterCommands();
     }
 
 }
--- a/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Wed May 30 20:14:55 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/agent/AgentApplication.java	Wed May 30 20:16:19 2012 +0200
@@ -178,6 +178,11 @@
     }
 
     @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/client/core/src/main/java/com/redhat/thermostat/client/GUIClientCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/GUIClientCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -69,6 +69,11 @@
     }
 
     @Override
+    public void disable() {
+        // TODO clientMain.shutdown();
+    }
+
+    @Override
     public String getName() {
         return "gui";
     }
--- a/client/core/src/main/java/com/redhat/thermostat/client/MainView.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/MainView.java	Wed May 30 20:16:19 2012 +0200
@@ -37,7 +37,10 @@
 package com.redhat.thermostat.client;
 
 import java.awt.Component;
+import java.awt.event.MouseEvent;
+import java.util.List;
 
+import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.dao.Ref;
@@ -54,6 +57,7 @@
         SWITCH_HISTORY_MODE,
         SHOW_ABOUT_DIALOG,
         SHUTDOWN,
+        SHOW_VM_CONTEXT_MENU,
         VM_CONTEXT_ACTION,
     }
 
@@ -73,5 +77,9 @@
 
     void setSubView(Component view);
 
-    void registerVMContextAction(VMContextAction action);
+    void addMenu(String parentMenuName, MenuAction action);
+
+    void removeMenu(String parentMenuName, MenuAction action);
+
+    void showVMContextActions(List<VMContextAction> actions, MouseEvent e);
 }
--- a/client/core/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Wed May 30 20:16:19 2012 +0200
@@ -36,13 +36,16 @@
 
 package com.redhat.thermostat.client;
 
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.osgi.framework.BundleException;
-
+import com.redhat.thermostat.client.MainView.Action;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.ui.AboutDialog;
 import com.redhat.thermostat.client.ui.AgentConfigurationController;
@@ -65,6 +68,7 @@
 import com.redhat.thermostat.common.dao.Ref;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmInfo;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
 public class MainWindowControllerImpl implements MainWindowController {
@@ -83,13 +87,27 @@
     private ApplicationInfo appInfo;
 
     private UiFacadeFactory facadeFactory;
+    private MenuRegistry menuRegistry;
+    private MenuRegistry.MenuListener menuListener = new MenuRegistry.MenuListener() {
+
+        @Override
+        public void removed(String parentMenuName, MenuAction action) {
+            view.removeMenu(parentMenuName, action);
+        }
+
+        @Override
+        public void added(String parentMenuName, MenuAction action) {
+            view.addMenu(parentMenuName, action);
+        }
+    };
 
     private boolean showHistory;
 
     private VmInformationControllerProvider vmInfoControllerProvider;
-    
-    public MainWindowControllerImpl(UiFacadeFactory facadeFactory, MainView view) {
+
+    public MainWindowControllerImpl(UiFacadeFactory facadeFactory, MainView view, MenuRegistry menuRegistry) {
         this.facadeFactory = facadeFactory;
+        this.menuRegistry = menuRegistry;
 
         ApplicationContext ctx = ApplicationContext.getInstance();
         DAOFactory daoFactory = ctx.getDAOFactory();
@@ -104,12 +122,10 @@
         view.setWindowTitle(appInfo.getName());
         initializeTimer();
 
-        logger.log(Level.INFO, "registering VMContextActions actions to view");
-        for (VMContextAction action : facadeFactory.getVMContextActions()) {
-            view.registerVMContextAction(action);
-        }
-        
         updateView();
+
+        menuRegistry.start();
+        menuRegistry.addMenuListener(menuListener);
     }
 
     private class HostsVMsLoaderImpl implements HostsVMsLoader {
@@ -197,6 +213,9 @@
                 case SHOW_ABOUT_DIALOG:
                     showAboutDialog();
                     break;
+                case SHOW_VM_CONTEXT_MENU:
+                    showContextMenu(evt);
+                    break;
                 case VM_CONTEXT_ACTION:
                     handleVMHooks(evt);
                     break;
@@ -212,6 +231,10 @@
     }
 
     private void shutdownApplication() {
+        menuRegistry.removeMenuListener(menuListener);
+        menuListener = null;
+        menuRegistry.stop();
+
         view.hideMainWindow();
         ApplicationContext.getInstance().getTimerFactory().shutdown();
         shutdownOSGiFramework();
@@ -221,6 +244,21 @@
         facadeFactory.shutdown();
     }
 
+    private void showContextMenu(ActionEvent<Action> evt) {
+        List<VMContextAction> toShow = new ArrayList<>();
+        VmRef vm = (VmRef) view.getSelectedHostOrVm();
+
+        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());
+    }
+
     private void handleVMHooks(ActionEvent<MainView.Action> event) {
         Object payload = event.getPayload();
         if (payload instanceof VMContextAction) { 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/MenuRegistry.java	Wed May 30 20:16:19 2012 +0200
@@ -0,0 +1,142 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.client.osgi.service.MenuAction;
+
+public class MenuRegistry {
+
+    public static interface MenuListener {
+
+        public void added(String parentMenuName, MenuAction action);
+
+        public void removed(String parentMenuName, MenuAction action);
+    }
+
+    public static final String PARENT_MENU = "parentMenu";
+
+    private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" + MenuAction.class.getName() + ")(" + PARENT_MENU + "=*))";
+
+    private ServiceTracker menuTracker;
+
+    private Map<String,List<MenuAction>> menus = new HashMap<>();
+    private List<MenuListener> listeners = new CopyOnWriteArrayList<>();
+
+    public MenuRegistry(BundleContext context) throws InvalidSyntaxException {
+        menuTracker = new ServiceTracker(context, FrameworkUtil.createFilter(FILTER), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                MenuAction action = (MenuAction) super.addingService(reference);
+                String parentMenuName = (String) reference.getProperty(PARENT_MENU);
+                menuAdded(parentMenuName, action);
+                return action;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference, Object service) {
+                if (!(service instanceof MenuAction)) {
+                    throw new AssertionError("removing a non-MenuAction service");
+                }
+                String parentMenuName = (String) reference.getProperty(PARENT_MENU);
+                menuRemoved(parentMenuName, (MenuAction)service);
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+    public void start() {
+        menuTracker.open();
+    }
+
+    public void stop() {
+        menuTracker.close();
+    }
+
+    public void addMenuListener(MenuListener listener) {
+        listeners.add(listener);
+
+        for (Entry<String,List<MenuAction>> entry: menus.entrySet()) {
+            for (MenuAction action: entry.getValue()) {
+                listener.added(entry.getKey(), action);
+            }
+        }
+    }
+
+    public void removeMenuListener(MenuListener listener) {
+        listeners.remove(listener);
+    }
+
+    private void menuAdded(String parentMenuName, MenuAction action) {
+        if (!menus.containsKey(parentMenuName)) {
+            menus.put(parentMenuName, new ArrayList<MenuAction>());
+        }
+        List<MenuAction> list = menus.get(parentMenuName);
+        list.add(action);
+        for (MenuListener listener: listeners) {
+            listener.added(parentMenuName, action);
+        }
+    }
+
+    private void menuRemoved(String parentMenuName, MenuAction action) {
+        if (!menus.containsKey(parentMenuName)) {
+            throw new IllegalArgumentException("unknown parent menu name");
+        }
+        List<MenuAction> list = menus.get(parentMenuName);
+        if (!list.contains(action)) {
+            throw new IllegalArgumentException("unknown menu action");
+        }
+
+        list.remove(action);
+        for (MenuListener listener: listeners) {
+            listener.removed(parentMenuName, action);
+        }
+    }
+
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Wed May 30 20:16:19 2012 +0200
@@ -40,8 +40,6 @@
 import java.util.Collection;
 import java.util.concurrent.CountDownLatch;
 
-import org.osgi.framework.BundleContext;
-
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.osgi.service.VmInformationService;
 import com.redhat.thermostat.client.ui.HostInformationController;
@@ -57,11 +55,17 @@
 
     private Collection<VmInformationService> vmInformationServices = new ArrayList<>();
     private Collection<VMContextAction> contextAction = new ArrayList<>();
-    
+
+    private MenuRegistry menuRegistry;
+
+    public UiFacadeFactoryImpl(MenuRegistry registry) {
+        menuRegistry = registry;
+    }
+
     @Override
     public MainWindowController getMainWindow() {
         MainView mainView = new MainWindow();
-        return new MainWindowControllerImpl(this, mainView);
+        return new MainWindowControllerImpl(this, mainView, menuRegistry);
     }
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/ThermostatActivator.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/ThermostatActivator.java	Wed May 30 20:16:19 2012 +0200
@@ -43,27 +43,32 @@
 
 import com.redhat.thermostat.client.GUIClientCommand;
 import com.redhat.thermostat.client.Main;
+import com.redhat.thermostat.client.MenuRegistry;
 import com.redhat.thermostat.client.UiFacadeFactory;
 import com.redhat.thermostat.client.UiFacadeFactoryImpl;
+import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
 public class ThermostatActivator implements BundleActivator {
 
     private VmInformationServiceTracker vmInfoServiceTracker;
     private VMContextActionServiceTracker contextActionTracker;
-    
+
+    private CommandRegistry cmdReg;
+
     @Override
     public void start(final BundleContext context) throws Exception {
-        UiFacadeFactory uiFacadeFactory = new UiFacadeFactoryImpl();
-        
+        MenuRegistry menuRegistry = new MenuRegistry(context);
+        UiFacadeFactory uiFacadeFactory = new UiFacadeFactoryImpl(menuRegistry);
+
         vmInfoServiceTracker = new VmInformationServiceTracker(context, uiFacadeFactory);
         vmInfoServiceTracker.open();
-        
+
         contextActionTracker =
                 new VMContextActionServiceTracker(context, uiFacadeFactory);
         contextActionTracker.open();
 
-        CommandRegistryImpl cmdReg = new CommandRegistryImpl(context);
+        cmdReg = new CommandRegistryImpl(context);
         Main main = new Main(uiFacadeFactory, new String[0]);
         cmdReg.registerCommands(Arrays.asList(new GUIClientCommand(main)));
     }
@@ -72,5 +77,6 @@
     public void stop(BundleContext context) throws Exception {
         vmInfoServiceTracker.close(); //context.removeServiceListener(vmInfoServiceTracker);
         contextActionTracker.close();
+        cmdReg.unregisterCommands();
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/MenuAction.java	Wed May 30 20:16:19 2012 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * Allows plugins to register menu items.
+ * <p>
+ * To register a menu item for for the menu "File" in thermostat client window,
+ * register a service that implements this class with the property
+ * "parentMenu" set to "File".
+ */
+public interface MenuAction {
+
+    /** The string displayed as the menu item name */
+    String getName();
+
+    /** A generic description of the menu item */
+    String getDescription();
+
+    /** Invoked when the user selects this menu item */
+    void execute();
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMContextAction.java	Wed May 30 20:16:19 2012 +0200
@@ -38,7 +38,12 @@
 
 import com.redhat.thermostat.common.dao.VmRef;
 
+/**
+ * A context action for VMs
+ */
 public interface VMContextAction extends ContextAction {
 
     void execute(VmRef referece);
+
+    VMFilter getFilter();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/VMFilter.java	Wed May 30 20:16:19 2012 +0200
@@ -0,0 +1,44 @@
+/*
+ * 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.common.dao.VmRef;
+
+public interface VMFilter {
+
+    boolean matches(VmRef vm);
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Wed May 30 20:16:19 2012 +0200
@@ -74,6 +74,7 @@
 import javax.swing.JTextField;
 import javax.swing.JTree;
 import javax.swing.KeyStroke;
+import javax.swing.MenuElement;
 import javax.swing.SwingUtilities;
 import javax.swing.SwingWorker;
 import javax.swing.ToolTipManager;
@@ -95,6 +96,7 @@
 import com.redhat.thermostat.client.HostsVMsLoader;
 import com.redhat.thermostat.client.MainView;
 import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
@@ -221,6 +223,7 @@
 
     private static final long serialVersionUID = 5608972421496808177L;
 
+    private final JMenuBar mainMenuBar;
     private JPanel contentArea = null;
 
     private JTextField searchField = null;
@@ -251,6 +254,8 @@
         ToolTipManager.sharedInstance().registerComponent(agentVmTree);
         contentArea = new JPanel(new BorderLayout());
 
+        mainMenuBar = new JMenuBar();
+
         setupMenus();
         setupPanels();
 
@@ -280,7 +285,6 @@
     }
 
     private void setupMenus() {
-        JMenuBar mainMenuBar = new JMenuBar();
 
         JMenu fileMenu = new JMenu(localize(LocaleResources.MENU_FILE));
         fileMenu.getPopupMenu().setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 1));
@@ -395,7 +399,7 @@
                 }
             }
         });
-        setupContextActions(agentVmTree);
+        registerContextActionListener(agentVmTree);
         
         JScrollPane treeScrollPane = new JScrollPane(agentVmTree);
 
@@ -412,14 +416,14 @@
         add(splitPane);
     }
 
-    private void setupContextActions(JTree agentVmTree2) {
+    private void registerContextActionListener(JTree agentVmTree2) {
         vmContextMenu = new JPopupMenu();
         agentVmTree2.addMouseListener(new MouseAdapter() {
             public void mousePressed(MouseEvent e) {
                 if (e.isPopupTrigger()) {
                     Ref ref = getSelectedHostOrVm();
                     if (ref instanceof VmRef) {
-                        vmContextMenu.show((Component)e.getSource(), e.getX(), e.getY());
+                        fireViewAction(Action.SHOW_VM_CONTEXT_MENU, e);
                     }
                 }
             }
@@ -427,18 +431,30 @@
     }
 
     @Override
-    public void registerVMContextAction(final VMContextAction action) {
-        
-        JMenuItem contextAction = new JMenuItem();
-        contextAction.setText(action.getName());
-        contextAction.setToolTipText(action.getDescription());
-        vmContextMenu.add(contextAction);
-        
-        contextAction.addActionListener(new java.awt.event.ActionListener() {
+    public void showVMContextActions(final List<VMContextAction> actions, final MouseEvent e) {
+        SwingUtilities.invokeLater(new Runnable() {
+
             @Override
-            public void actionPerformed(ActionEvent e) {
-                fireViewAction(Action.VM_CONTEXT_ACTION, action);
+            public void run() {
+                vmContextMenu.removeAll();
+
+                for (final VMContextAction action: actions) {
+                    JMenuItem contextAction = new JMenuItem();
+                    contextAction.setText(action.getName());
+                    contextAction.setToolTipText(action.getDescription());
+
+                    contextAction.addActionListener(new java.awt.event.ActionListener() {
+                        @Override
+                        public void actionPerformed(ActionEvent e) {
+                            fireViewAction(Action.VM_CONTEXT_ACTION, action);
+                        }
+                    });
+                    vmContextMenu.add(contextAction);
+                }
+
+                vmContextMenu.show((Component)e.getSource(), e.getX(), e.getY());
             }
+
         });
     }
     
@@ -561,7 +577,7 @@
         actionNotifier.fireAction(action);
     }
     
-    private void fireViewAction(Action action, VMContextAction payload) {
+    private void fireViewAction(Action action, Object payload) {
         actionNotifier.fireAction(action, payload);
     }
     
@@ -623,6 +639,92 @@
         });
     }
 
+    @Override
+    public void addMenu(final String parentMenuName, final MenuAction action) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                JMenu parent = null;
+                int mainMenuCount = mainMenuBar.getMenuCount();
+                for (int i = 0; i < mainMenuCount; i++) {
+                    if (mainMenuBar.getMenu(i).getText().equals(parentMenuName)) {
+                        parent = mainMenuBar.getMenu(i);
+                        break;
+                    }
+                }
+                if (parent == null) {
+                    parent = new JMenu(parentMenuName);
+                    mainMenuBar.add(parent);
+                }
+
+                JMenuItem menu = new JMenuItem(action.getName());
+                menu.addActionListener(new java.awt.event.ActionListener() {
+                    @Override
+                    public void actionPerformed(ActionEvent e) {
+                        action.execute();
+                    }
+                });
+                parent.add(menu);
+
+                mainMenuBar.revalidate();
+            }
+        });
+    }
+
+    @Override
+    public void removeMenu(final String parentMenuName, final  MenuAction action) {
+        final String actionName = action.getName();
+        try {
+            new EdtHelper().callAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    MenuElement parent = null;
+                    int mainMenuCount = mainMenuBar.getMenuCount();
+                    for (int i = 0; i < mainMenuCount; i++) {
+                        if (mainMenuBar.getMenu(i).getText().equals(parentMenuName)) {
+                            parent = mainMenuBar.getMenu(i);
+                            break;
+                        }
+                    }
+                    if (parent == null) {
+                        throw new IllegalArgumentException("parent menu not found");
+                    }
+
+                    boolean removed = false;
+                    MenuElement[] menus = parent.getSubElements();
+                    if (menus.length == 1 && (menus[0] instanceof JPopupMenu)) {
+                        parent = menus[0];
+                        menus = parent.getSubElements();
+                    }
+
+                    for (MenuElement menu: menus) {
+                        if (menu instanceof JMenuItem && ((JMenuItem)menu).getText().equals(actionName)) {
+                            if (parent instanceof JPopupMenu) {
+                                ((JPopupMenu)parent).remove((JMenuItem)menu);
+                                removed = true;
+                            }
+                        }
+                    }
+
+                    if (!removed) {
+                        throw new IllegalArgumentException("child menu not found");
+                    }
+
+                    mainMenuBar.revalidate();
+                }
+            });
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(ie);
+        } catch (InvocationTargetException roe) {
+            Throwable cause = roe.getCause();
+            if (cause instanceof IllegalArgumentException) {
+                throw (IllegalArgumentException) cause;
+            }
+            throw new RuntimeException(cause);
+        }
+    }
+
     /**
      * Returns null to indicate no Ref is selected
      */
--- a/client/core/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Wed May 30 20:16:19 2012 +0200
@@ -37,19 +37,23 @@
 package com.redhat.thermostat.client;
 
 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.eq;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.awt.Component;
+import java.awt.event.MouseEvent;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.TimeUnit;
 
@@ -59,11 +63,12 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 
+import com.redhat.thermostat.client.MenuRegistry.MenuListener;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
+import com.redhat.thermostat.client.osgi.service.VMFilter;
 import com.redhat.thermostat.client.ui.SummaryController;
 import com.redhat.thermostat.client.ui.SummaryView;
 import com.redhat.thermostat.client.ui.VmInformationController;
@@ -80,6 +85,7 @@
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmInfo;
 import com.redhat.thermostat.test.Bug;
 
 public class MainWindowControllerImplTest {
@@ -99,7 +105,9 @@
 
     private VMContextAction action1;
     private VMContextAction action2;
-    
+
+    private MenuListener menuListener;
+
     @BeforeClass
     public static void setUpOnce() {
         // TODO remove when controller uses mocked objects rather than real swing objects
@@ -129,6 +137,10 @@
         ArgumentCaptor<ActionListener> grabListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(grabListener.capture());
 
+        MenuRegistry registry = mock(MenuRegistry.class);
+        ArgumentCaptor<MenuListener> menuListenerCaptor = ArgumentCaptor.forClass(MenuListener.class);
+        doNothing().when(registry).addMenuListener(menuListenerCaptor.capture());
+
         // TODO remove this asap. the main window has a hard dependency on summary controller/view
         ViewFactory viewFactory = mock(ViewFactory.class);
         SummaryView summaryView = mock(SummaryView.class);
@@ -136,20 +148,29 @@
         ApplicationContext.getInstance().setViewFactory(viewFactory);
 
         setUpVMContextActions();
-        
-        controller = new MainWindowControllerImpl(uiFacadeFactory, view);
+
+        controller = new MainWindowControllerImpl(uiFacadeFactory, view, registry);
         l = grabListener.getValue();
+        menuListener = menuListenerCaptor.getValue();
 
     }
 
     private void setUpVMContextActions() {
         action1 = mock(VMContextAction.class);
+        VMFilter action1Filter = mock(VMFilter.class);
+        when(action1Filter.matches(isA(VmRef.class))).thenReturn(true);
+
         when(action1.getName()).thenReturn("action1");
         when(action1.getDescription()).thenReturn("action1desc");
+        when(action1.getFilter()).thenReturn(action1Filter);
         
         action2 = mock(VMContextAction.class);
+        VMFilter action2Filter = mock(VMFilter.class);
+        when(action2Filter.matches(isA(VmRef.class))).thenReturn(false);
+
         when(action2.getName()).thenReturn("action2");
         when(action2.getDescription()).thenReturn("action2desc");
+        when(action2.getFilter()).thenReturn(action2Filter);
         
         Collection<VMContextAction> actions = new ArrayList<>();
         actions.add(action1);
@@ -325,12 +346,22 @@
 
         assertEquals(3, id);
     }
-    
+
     @Test
-    public void verityVMActionsAreRegistered() {
+    public void verityVMActionsAreShown() {
+        VmInfo vmInfo = new VmInfo(0, 1, 2, null, null, null, null, null, null, null, null, null, null, null);
+        when(mockVmsDAO.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
+
+        VmRef ref = mock(VmRef.class);
+        when(view.getSelectedHostOrVm()).thenReturn(ref);
 
-        verify(view).registerVMContextAction(action1);
-        verify(view).registerVMContextAction(action2);
+        MouseEvent uiEvent = mock(MouseEvent.class);
+        ActionEvent<MainView.Action> viewEvent = new ActionEvent<>(view, MainView.Action.SHOW_VM_CONTEXT_MENU);
+        viewEvent.setPayload(uiEvent);
+
+        l.actionPerformed(viewEvent);
+
+        verify(view).showVMContextActions(Arrays.asList(action1), uiEvent);
     }
     
     @Test
@@ -347,6 +378,19 @@
         verify(action2, times(0)).execute(any(VmRef.class));
     }
 
+    @Test
+    public void verifyMenuItems() {
+        assertNotNull(menuListener);
+        MenuAction action = mock(MenuAction.class);
+        when(action.getName()).thenReturn("Test1");
+
+        menuListener.added("File", action);
+        verify(view).addMenu("File", action);
+
+        menuListener.removed("File", action);
+        verify(view).removeMenu("File", action);
+    }
+
    @Test
    public void testOSGiFrameworkShutdown() throws BundleException {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/test/java/com/redhat/thermostat/client/MenuRegistryTest.java	Wed May 30 20:16:19 2012 +0200
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+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 org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+import com.redhat.thermostat.client.MenuRegistry.MenuListener;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
+
+public class MenuRegistryTest {
+
+    @Test
+    public void verifyMenuRegistryReactsToMenuActions() throws InvalidSyntaxException {
+        ArgumentCaptor<ServiceListener> serviceListenerCaptor = ArgumentCaptor.forClass(ServiceListener.class);
+        ArgumentCaptor<String> filterCaptor = ArgumentCaptor.forClass(String.class);
+
+        MenuListener menuListener = mock(MenuListener.class);
+        MenuAction menuAction = mock(MenuAction.class);
+
+        BundleContext context = mock(BundleContext.class);
+        doNothing().when(context).addServiceListener(serviceListenerCaptor.capture(), filterCaptor.capture());
+
+        ServiceReference ref = mock(ServiceReference.class);
+        when(ref.getProperty("objectClass")).thenReturn(MenuAction.class.getName());
+        when(ref.getProperty(MenuRegistry.PARENT_MENU)).thenReturn("Test");
+
+        when(context.getService(ref)).thenReturn(menuAction);
+
+        MenuRegistry registry = new MenuRegistry(context);
+        registry.addMenuListener(menuListener);
+        registry.start();
+
+        ServiceListener serviceListener = serviceListenerCaptor.getValue();
+        serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, ref));
+
+        verify(menuListener).added(eq("Test"), isA(MenuAction.class));
+
+        serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));
+
+        verify(menuListener).removed(eq("Test"), isA(MenuAction.class));
+
+    }
+}
--- a/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Wed May 30 20:16:19 2012 +0200
@@ -37,15 +37,23 @@
 package com.redhat.thermostat.client.ui;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import javax.swing.AbstractAction;
+
 import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
 
 import org.fest.swing.annotation.GUITest;
 import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
 import org.fest.swing.edt.GuiActionRunner;
 import org.fest.swing.edt.GuiTask;
+import org.fest.swing.exception.ComponentLookupException;
 import org.fest.swing.fixture.FrameFixture;
 import org.fest.swing.fixture.JMenuItemFixture;
 import org.fest.swing.fixture.JTextComponentFixture;
@@ -58,6 +66,7 @@
 import org.junit.runner.RunWith;
 
 import com.redhat.thermostat.client.MainView;
+import com.redhat.thermostat.client.osgi.service.MenuAction;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 
@@ -191,7 +200,38 @@
 
         verify(l).actionPerformed(new ActionEvent<MainView.Action>(window, MainView.Action.SWITCH_HISTORY_MODE));
     }
-    
+
+    @Category(GUITest.class)
+    @Test
+    public void addRemoveMenu() {
+        final String MENU_NAME = "Test";
+        MenuAction action = mock(MenuAction.class);
+        when(action.getName()).thenReturn(MENU_NAME);
+
+        JMenuItemFixture menuItem;
+
+        frameFixture.show();
+
+        window.addMenu("File", action);
+
+        menuItem = frameFixture.menuItemWithPath("File", MENU_NAME);
+        assertNotNull(menuItem);
+        menuItem.click();
+
+        verify(action).execute();
+
+        window.removeMenu("File", action);
+
+        try {
+            menuItem = frameFixture.menuItemWithPath("File", MENU_NAME);
+            // should not reach here
+            assertTrue(false);
+        } catch (ComponentLookupException cle) {
+            // expected
+        }
+
+    }
+
     @Category(GUITest.class)
     @Test
     public void testGetHostVMTreeFilter() {
--- a/client/killvm/src/main/java/com/redhat/thermostat/client/killvm/Activator.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/killvm/src/main/java/com/redhat/thermostat/client/killvm/Activator.java	Wed May 30 20:16:19 2012 +0200
@@ -45,6 +45,7 @@
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
 import com.redhat.thermostat.client.osgi.service.ContextAction;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.service.process.UNIXProcessHandler;
@@ -61,7 +62,8 @@
         ServiceListener listener = new ServiceListener() {
             
             private UNIXProcessHandler unixService;
-            private boolean[] loaded = new boolean[2];
+            private ApplicationService appService;
+            private boolean[] loaded = new boolean[3];
             
             @Override
             public void serviceChanged(ServiceEvent event) {
@@ -75,6 +77,9 @@
                     } else if (service instanceof UNIXProcessHandler) {
                         loaded[1] = true;
                         unixService = (UNIXProcessHandler) service;
+                    } else if (service instanceof ApplicationService) {
+                        loaded[2] = true;
+                        appService = (ApplicationService) service;
                     }
                     break;
 
@@ -82,16 +87,17 @@
                     break;
                 }
                 
-                if (loaded[0] && loaded[1]) {
+                if (loaded[0] && loaded[1] && loaded[2]) {
                     context.registerService(VMContextAction.class.getName(),
-                                            new KillVMAction(unixService), null);
+                                            new KillVMAction(unixService, appService.getDAOFactory()), null);
                 }
             }
         };
         
         try {
-            String filter = "(|(objectClass=" + ContextAction.class.getName() + 
-                            ")(objectClass=" + UNIXProcessHandler.class.getName() + "))";
+            String filter = "(|(objectClass=" + ContextAction.class.getName() + ")" +
+                            "  (objectClass=" + UNIXProcessHandler.class.getName() + ")" +
+                            "  (objectClass=" + ApplicationService.class.getName() + "))";
                     
             context.addServiceListener(listener, filter);
             ServiceReference[] services = context.getServiceReferences(null, null);
--- a/client/killvm/src/main/java/com/redhat/thermostat/client/killvm/KillVMAction.java	Wed May 30 20:14:55 2012 +0200
+++ b/client/killvm/src/main/java/com/redhat/thermostat/client/killvm/KillVMAction.java	Wed May 30 20:16:19 2012 +0200
@@ -37,7 +37,10 @@
 package com.redhat.thermostat.client.killvm;
 
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
+import com.redhat.thermostat.client.osgi.service.VMFilter;
+import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.VmInfo;
 import com.redhat.thermostat.service.process.UNIXProcessHandler;
 import com.redhat.thermostat.service.process.UNIXSignal;
 
@@ -47,12 +50,14 @@
  */
 public class KillVMAction implements VMContextAction {
 
-    UNIXProcessHandler unixService;
-    
-    public KillVMAction(UNIXProcessHandler unixService) {
+    private final UNIXProcessHandler unixService;
+    private final DAOFactory dao;
+
+    public KillVMAction(UNIXProcessHandler unixService, DAOFactory dao) {
         this.unixService = unixService;
+        this.dao = dao;
     }
-    
+
     @Override
     public String getName() {
         return "Kill Application";
@@ -67,4 +72,21 @@
     public void execute(VmRef reference) {
         unixService.sendSignal(reference.getIdString(), UNIXSignal.KILL);
     }
+
+    @Override
+    public VMFilter getFilter() {
+        return new LocalAndAliveFilter();
+    }
+
+    private class LocalAndAliveFilter implements VMFilter {
+
+        @Override
+        public boolean matches(VmRef vm) {
+            // TODO implement local checking too
+            VmInfo vmInfo = dao.getVmInfoDAO().getVmInfo(vm);
+            boolean dead = vmInfo.getStartTimeStamp() < vmInfo.getStopTimeStamp();
+            return !dead;
+        }
+
+    }
 }
--- a/common/src/main/java/com/redhat/thermostat/common/Activator.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/Activator.java	Wed May 30 20:16:19 2012 +0200
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.common;
 
-import java.util.Collection;
 import java.util.ServiceLoader;
 
 import org.osgi.framework.BundleActivator;
@@ -52,14 +51,14 @@
 
 public class Activator implements BundleActivator {
 
-    private Collection<ServiceRegistration> cmdRegs;
     private ServiceRegistration launcherReg;
+    private CommandRegistry reg;
 
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
+        reg = new CommandRegistryImpl(context);
         ServiceLoader<Command> cmds = ServiceLoader.load(Command.class, getClass().getClassLoader());
-        cmdRegs = reg.registerCommands(cmds);
+        reg.registerCommands(cmds);
 
         CommandContextFactory cmdCtxFactory = new CommandContextFactory(context);
         Launcher launcher = new LauncherImpl(cmdCtxFactory);
@@ -69,9 +68,7 @@
     @Override
     public void stop(BundleContext context) throws Exception {
         launcherReg.unregister();
-        for (ServiceRegistration cmdReg : cmdRegs) {
-            cmdReg.unregister();
-        }
+        reg.unregisterCommands();
     }
 
 }
--- a/common/src/main/java/com/redhat/thermostat/common/cli/Command.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/Command.java	Wed May 30 20:16:19 2012 +0200
@@ -38,19 +38,50 @@
 
 import java.util.Collection;
 
-
+/**
+ * Represents a command on the command line.
+ * <p>
+ * To register a custom command, have a class implement this interface and
+ * register it as an OSGi service with the {@link #NAME} set to the value of
+ * {@link #getName()}.
+ */
 public interface Command {
 
-    String NAME = "COMMAND_NAME";
+    static final String NAME = "COMMAND_NAME";
 
+    /**
+     * Execute the command
+     */
     void run(CommandContext ctx) throws CommandException;
 
+    /**
+     * Called when the command is being removed from the system. The command
+     * should cancel any long-term action it has taken, such as any background
+     * tasks or threads it has spawned.
+     */
+    void disable();
+
+    /**
+     * Returns a name for this command. This will be used by the user to select
+     * this command.
+     */
     String getName();
 
+    /**
+     * A short description for the command indicating what it does.
+     */
     String getDescription();
 
+    /**
+     * How the user should invoke this command
+     */
     String getUsage();
 
+    /**
+     * Returns a collection of arguments that the command is prepared to handle.
+     * If the user provides unknown or malformed arguments, this command will
+     * not be invoked.
+     */
     Collection<ArgumentSpec> getAcceptedArguments();
 
     boolean isStorageRequired();
--- a/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistry.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistry.java	Wed May 30 20:16:19 2012 +0200
@@ -44,6 +44,8 @@
 
     public abstract Collection<ServiceRegistration> registerCommands(Iterable<? extends Command> cmds);
 
+    public abstract void unregisterCommands();
+
     public abstract Command getCommand(String name);
 
     public abstract Collection<Command> getRegisteredCommands();
--- a/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Wed May 30 20:16:19 2012 +0200
@@ -39,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.logging.Logger;
 
@@ -53,6 +54,8 @@
 
     private BundleContext context;
 
+    private List<ServiceRegistration> ourRegistrations = new ArrayList<ServiceRegistration>();
+
     public CommandRegistryImpl(BundleContext ctx) {
         context = ctx;
     }
@@ -60,12 +63,27 @@
     protected ServiceRegistration registerCommand(Command cmd) {
         Hashtable<String, String> props = new Hashtable<>();
         props.put(Command.NAME, cmd.getName());
-        return context.registerService(Command.class.getName(), cmd, props);
+        ServiceRegistration registration = context.registerService(Command.class.getName(), cmd, props);
+        ourRegistrations.add(registration);
+        return registration;
+    }
+
+    @Override
+    public void unregisterCommands() {
+        Iterator<ServiceRegistration> iter = ourRegistrations.iterator();
+        while (iter.hasNext()) {
+            ServiceRegistration registration = iter.next();
+            Object serviceObject =  context.getService(registration.getReference());
+            Command cmd = (Command) serviceObject;
+            cmd.disable();
+            registration.unregister();
+            iter.remove();
+        }
     }
 
     @Override
     public Command getCommand(String name) {
-        ServiceReference[] refs = getCommandServiceRefs("(&(objectclass=*)(COMMAND_NAME=" + name + "))");
+        ServiceReference[] refs = getCommandServiceRefs("(&(objectclass=*)(" + Command.NAME + "=" + name + "))");
         if (refs == null || refs.length == 0) {
             return null;
         } else if (refs.length > 1) {
--- a/common/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/cli/HelpCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -92,6 +92,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/common/src/main/java/com/redhat/thermostat/test/TestCommandRegistry.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/test/TestCommandRegistry.java	Wed May 30 20:16:19 2012 +0200
@@ -66,7 +66,7 @@
         public void unregister() {
             // Nothing to do for now.
         }
-        
+
     }
 
     private Map<String, Command> commands = new HashMap<>();
@@ -83,4 +83,9 @@
         commands.put(cmd.getName(), cmd);
         return new TestServiceRegistration();
     }
+
+    @Override
+    public void unregisterCommands() {
+        commands.clear();
+    }
 }
--- a/common/src/test/java/com/redhat/thermostat/common/ActivatorTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/ActivatorTest.java	Wed May 30 20:16:19 2012 +0200
@@ -47,12 +47,16 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.common.cli.Command;
@@ -63,17 +67,31 @@
 
     @Test
     public void testRegisterServices() throws Exception {
-        final Collection<ServiceRegistration> regs = new ArrayList<>();
+        final Map<ServiceRegistration, Object> regs = new HashMap<>();
         BundleContext bCtx = mock(BundleContext.class);
         when(bCtx.registerService(anyString(), any(), any(Dictionary.class))).then(new Answer<ServiceRegistration>() {
 
             @Override
             public ServiceRegistration answer(InvocationOnMock invocation) throws Throwable {
                 ServiceRegistration reg = mock(ServiceRegistration.class);
-                regs.add(reg);
+                when(reg.getReference()).thenReturn(mock(ServiceReference.class));
+                regs.put(reg, invocation.getArguments()[1]);
                 return reg;
             }
         });
+        when(bCtx.getService(isA(ServiceReference.class))).then(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                ServiceReference ref = (ServiceReference) invocation.getArguments()[0];
+                for (Entry<ServiceRegistration,Object> registration: regs.entrySet()) {
+                    if (registration.getKey().getReference().equals(ref)) {
+                        return registration.getValue();
+                    }
+                }
+                return null;
+            }
+        });
+
         Activator activator = new Activator();
 
         activator.start(bCtx);
@@ -86,7 +104,7 @@
 
         activator.stop(bCtx);
 
-        for (ServiceRegistration reg : regs) {
+        for (ServiceRegistration reg : regs.keySet()) {
             verify(reg).unregister();
         }
     }
--- a/common/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Wed May 30 20:16:19 2012 +0200
@@ -40,6 +40,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -55,6 +56,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 
 public class CommandRegistryImplTest {
 
@@ -88,6 +90,51 @@
         props2.put(Command.NAME, "test2");
         verify(bundleContext).registerService(Command.class.getName(), cmd1, props1);
         verify(bundleContext).registerService(Command.class.getName(), cmd2, props2);
+
+        verifyNoMoreInteractions(bundleContext);
+
+    }
+
+    @Test
+    public void testUnregisterCommand() {
+        Command cmd1 = mock(Command.class);
+        when(cmd1.getName()).thenReturn("test1");
+        Command cmd2 = mock(Command.class);
+        when(cmd2.getName()).thenReturn("test2");
+
+        ServiceReference cmd1Reference = mock(ServiceReference.class);
+        ServiceReference cmd2Reference = mock(ServiceReference.class);
+
+        ServiceRegistration cmd1Reg = mock(ServiceRegistration.class);
+        when(cmd1Reg.getReference()).thenReturn(cmd1Reference);
+        ServiceRegistration cmd2Reg = mock(ServiceRegistration.class);
+        when(cmd2Reg.getReference()).thenReturn(cmd2Reference);
+
+        Hashtable<String,String> props1 = new Hashtable<>();
+        props1.put(Command.NAME, cmd1.getName());
+        Hashtable<String,String> props2 = new Hashtable<>();
+        props2.put(Command.NAME, cmd2.getName());
+
+        when(bundleContext.registerService(Command.class.getName(), cmd1, props1)).thenReturn(cmd1Reg);
+        when(bundleContext.registerService(Command.class.getName(), cmd2, props2)).thenReturn(cmd2Reg);
+
+        commandRegistry.registerCommands(Arrays.asList(cmd1, cmd2));
+
+        verify(bundleContext).registerService(Command.class.getName(), cmd1, props1);
+        verify(bundleContext).registerService(Command.class.getName(), cmd2, props2);
+
+        when(bundleContext.getService(eq(cmd1Reference))).thenReturn(cmd1);
+        when(bundleContext.getService(eq(cmd2Reference))).thenReturn(cmd2);
+
+        commandRegistry.unregisterCommands();
+
+        verify(bundleContext).getService(cmd1Reference);
+        verify(cmd1).disable();
+        verify(cmd1Reg).unregister();
+        verify(bundleContext).getService(cmd2Reference);
+        verify(cmd2).disable();
+        verify(cmd2Reg).unregister();
+
         verifyNoMoreInteractions(bundleContext);
     }
 
--- a/common/src/test/java/com/redhat/thermostat/common/cli/LauncherTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/LauncherTest.java	Wed May 30 20:16:19 2012 +0200
@@ -64,6 +64,9 @@
             ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
         }
 
+        @Override
+        public void stop() { /* N0-OP */ }
+
     }
 
     private static class TestCmd2 implements TestCommand.Handle {
@@ -72,6 +75,11 @@
             Arguments args = ctx.getArguments();
             ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
         }
+
+        @Override
+        public void stop() {
+            /* NO-OP */
+        }
     }
 
     private TestCommandContextFactory  ctxFactory;
@@ -208,6 +216,9 @@
             public void run(CommandContext ctx) throws CommandException {
                 throw new CommandException("test error");
             }
+
+            @Override
+            public void stop() { /* NO-OP */ }
         });
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
 
--- a/common/src/test/java/com/redhat/thermostat/common/cli/TestCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/cli/TestCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -54,6 +54,7 @@
 
     static interface Handle {
         public void run(CommandContext ctx) throws CommandException;
+        public void stop();
     }
 
     TestCommand(String name) {
@@ -73,6 +74,13 @@
     }
 
     @Override
+    public void disable() {
+        if (handle != null) {
+            handle.stop();
+        }
+    }
+
+    @Override
     public String getName() {
         return name;
     }
--- a/common/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Wed May 30 20:14:55 2012 +0200
+++ b/common/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Wed May 30 20:16:19 2012 +0200
@@ -64,6 +64,11 @@
             }
 
             @Override
+            public void disable() {
+                // Move along
+            }
+
+            @Override
             public String getName() {
                 return null;
             }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/Thermostat.java	Wed May 30 20:14:55 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/Thermostat.java	Wed May 30 20:16:19 2012 +0200
@@ -40,6 +40,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -54,13 +55,15 @@
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 
-
 public class Thermostat {
 
     /**
-     * 
+     * the name of the launcher class
      */
     private static final String LAUNCHER_CLASSNAME = "com.redhat.thermostat.common.cli.Launcher";
+
+    private static final String DEBUG_PREFIX = "OSGi Launcher: ";
+
     private File thermostatBundleHome;
     private boolean printOSGiDebugInfo = false;
 
@@ -80,7 +83,7 @@
         BundleContext bundleContext = framework.getBundleContext();
         for (String location : bundleLocations) {
             if (printOSGiDebugInfo) {
-                System.out.print("installing bundle: \"" + location + "\"");
+                System.out.print(DEBUG_PREFIX + "installing bundle: \"" + location + "\"");
             }
             Bundle bundle = bundleContext.installBundle(location);
             if (printOSGiDebugInfo) {
@@ -95,7 +98,7 @@
     private void startBundles(List<Bundle> bundles) throws BundleException {
         for (Bundle bundle : bundles) {
             if (printOSGiDebugInfo) {
-                System.out.println("starting bundle: \"" + bundle.getBundleId() + "\"");
+                System.out.println(DEBUG_PREFIX + "starting bundle: \"" + bundle.getBundleId() + "\"");
             }
             bundle.start();
         }
@@ -117,11 +120,27 @@
         if (factories.hasNext()) {
 
             // we just want the first found
-            Framework framework = factories.next().newFramework(bundleConfigurations);
+            final Framework framework = factories.next().newFramework(bundleConfigurations);
             framework.init();
             List<String> bundles = OSGiRegistry.getSystemBundles();
             List<Bundle> bundleList = installBundles(framework, bundles);
             framework.start();
+
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                @Override
+                public void run() {
+                    try {
+                        framework.stop();
+                        framework.waitForStop(0);
+                        if (printOSGiDebugInfo) {
+                            System.out.println(DEBUG_PREFIX + "OSGi framework has shut down");
+                        }
+                    } catch (Exception e) {
+                        System.err.println("Error stopping framework:" + e);
+                    }
+                }
+            });
+
             startBundles(bundleList);
 
             launch(args, framework);
@@ -141,6 +160,9 @@
         if (launcherRef != null) {
             Object launcherImpl = ctx.getService(launcherRef);
             Method m = launcherImpl.getClass().getMethod("run", String[].class);
+            if (printOSGiDebugInfo) {
+                System.out.println(DEBUG_PREFIX + "invoking " + launcherImpl.getClass().getName() + "." + m.getName());
+            }
             m.invoke(launcherImpl, (Object) args);
         } else {
             System.err.println("Severe: Could not locate launcher");
@@ -158,10 +180,17 @@
     public static void main(String[] args) throws Exception {
 
         Thermostat t = new Thermostat();
-        if (args.length >= 1 && args[0].equals("--print-osgi-info")) {
-            t.setPrintOSGiDebugInfo(true);
+        List<String> toProcess = new ArrayList<>(Arrays.asList(args));
+        Iterator<String> iter = toProcess.iterator();
+        while (iter.hasNext()) {
+            String arg = iter.next();
+            if (("--print-osgi-info").equals(arg)) {
+                t.setPrintOSGiDebugInfo(true);
+                iter.remove();
+            }
         }
-        t.start(args);
+
+        t.start(toProcess.toArray(new String[0]));
     }
 
 
--- a/tools/src/main/java/com/redhat/thermostat/tools/Activator.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/Activator.java	Wed May 30 20:16:19 2012 +0200
@@ -47,17 +47,18 @@
 
 public class Activator implements BundleActivator {
 
+    private CommandRegistry reg;
+
     @Override
     public void start(BundleContext context) throws Exception {
-        CommandRegistry reg = new CommandRegistryImpl(context);
+        reg = new CommandRegistryImpl(context);
         ServiceLoader<Command> cmds = ServiceLoader.load(Command.class, getClass().getClassLoader());
         reg.registerCommands(cmds);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        // TODO Auto-generated method stub
-
+        reg.unregisterCommands();
     }
 
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/ThermostatService.java	Wed May 30 20:16:19 2012 +0200
@@ -38,6 +38,8 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -80,6 +82,8 @@
 
     private CommandContext context;
 
+    private List<Runnable> tasksOnStop = new CopyOnWriteArrayList<>();
+
     public ThermostatService() {
         database = new DBService();
         agent = new AgentApplication();
@@ -125,8 +129,8 @@
             // we started the database ourselves
             case START:
                 
-                // set a shutdown hook if the db was started by us
-                Runtime.getRuntime().addShutdownHook(new Thread() {
+                // set a bundle-stop hook if the db was started by us
+                tasksOnStop.add(new Runnable() {
                     @Override
                     public void run() {
                         String[] args = new String[] { "storage", "--stop" };
@@ -147,6 +151,7 @@
                     notifier.fireAction(ApplicationState.FAIL);
                 }
                 break;
+
             case FAIL:
                 System.err.println("error starting db");
                 notifier.fireAction(ApplicationState.FAIL);
@@ -163,6 +168,13 @@
     }
 
     @Override
+    public void disable() {
+        for (Runnable task: tasksOnStop) {
+            task.run();
+        }
+    }
+
+    @Override
     public String getName() {
         return NAME;
     }
@@ -183,4 +195,6 @@
         ArgumentSpec stop = new SimpleArgumentSpec("stop", "stop the database and agent");
         return Arrays.asList(start, stop);
     }
+
+
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ListVMsCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -77,6 +77,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/ShellCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ShellCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -121,6 +121,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMInfoCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -88,6 +88,9 @@
     }
 
     @Override
+    public void disable() { /* NO-OP */ }
+
+    @Override
     public String getName() {
         return NAME;
     }
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java	Wed May 30 20:16:19 2012 +0200
@@ -204,4 +204,9 @@
         return true;
     }
 
+    @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
 }
--- a/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/db/DBService.java	Wed May 30 20:16:19 2012 +0200
@@ -183,7 +183,12 @@
             throw new InvalidConfigurationException("database directories do not exist...");
         }
     }
-    
+
+    @Override
+    public void disable() {
+        /* NO-OP */
+    }
+
     @Override
     public DBStartupConfiguration getConfiguration() {
         return configuration;
--- a/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Wed May 30 20:14:55 2012 +0200
+++ b/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Wed May 30 20:16:19 2012 +0200
@@ -1,6 +1,5 @@
 com.redhat.thermostat.tools.ThermostatService
 com.redhat.thermostat.tools.db.DBService
-#com.redhat.thermostat.agent.AgentApplication
 com.redhat.thermostat.tools.cli.ListVMsCommand
 com.redhat.thermostat.tools.cli.ShellCommand
 com.redhat.thermostat.tools.cli.VMInfoCommand