changeset 362:00ffcc68584f

Add VMTree label annotations and some tests. review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-June/001771.html reviewed-by: omajid PR 1002
author Mario Torre <neugens.limasoftware@gmail.com>
date Tue, 12 Jun 2012 11:30:24 +0200
parents 2e886e58a4d6
children e087c8526a58
files client/core/src/main/java/com/redhat/thermostat/client/MainView.java client/core/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java client/core/src/main/java/com/redhat/thermostat/client/RegistryFactory.java client/core/src/main/java/com/redhat/thermostat/client/ThermostatExtensionRegistry.java client/core/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java client/core/src/main/java/com/redhat/thermostat/client/VMTreeDecoratorRegistry.java client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ReferenceDecorator.java client/core/src/main/java/com/redhat/thermostat/client/ui/DecoratedDefaultMutableTreeNode.java client/core/src/main/java/com/redhat/thermostat/client/ui/Decorator.java client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java client/core/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/DeadVMDecorator.java client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMDecorator.java client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMFilterActivator.java
diffstat 15 files changed, 664 insertions(+), 73 deletions(-) [+]
line wrap: on
line diff
--- a/client/core/src/main/java/com/redhat/thermostat/client/MainView.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/MainView.java	Tue Jun 12 11:30:24 2012 +0200
@@ -41,6 +41,7 @@
 import java.util.List;
 
 import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.common.ActionListener;
@@ -64,7 +65,7 @@
 
     void addActionListener(ActionListener<Action> capture);
 
-    void updateTree(List<Filter> filter, HostsVMsLoader any);
+    void updateTree(List<Filter> filter, List<ReferenceDecorator> decorators, HostsVMsLoader any);
     String getHostVmTreeFilterText();
     
     void setWindowTitle(String title);
--- a/client/core/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/MainWindowControllerImpl.java	Tue Jun 12 11:30:24 2012 +0200
@@ -51,6 +51,7 @@
 import com.redhat.thermostat.client.MainView.Action;
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.ui.AboutDialog;
 import com.redhat.thermostat.client.ui.AgentConfigurationController;
@@ -79,7 +80,9 @@
 
     private static final Logger logger = LoggingUtils.getLogger(MainWindowControllerImpl.class);
     
-    private List<Filter> filters;
+    private List<Filter> vmTreefilters;
+    private List<ReferenceDecorator> vmTreeDecorators;
+    
     private Timer backgroundUpdater;
 
     private MainView view;
@@ -106,28 +109,85 @@
         }
     };
 
-    private VMTreeFilterRegistry filterRegistry;
+    // FIXME: sort out the code duplication in the registry listeners
     
     private TreeViewFilter treeFilter;
+    private VMTreeFilterRegistry filterRegistry;
+    private ActionListener<ThermostatExtensionRegistry.Action> filterListener =
+            new ActionListener<ThermostatExtensionRegistry.Action>()
+    {
+        @Override
+        public void actionPerformed(ActionEvent<com.redhat.thermostat.client.ThermostatExtensionRegistry.Action>
+                                    actionEvent)
+        {
+            Filter filter = (Filter) actionEvent.getPayload();
+            
+            switch (actionEvent.getActionId()) {
+            case SERVICE_ADDED:
+                vmTreefilters.add(filter);
+                doUpdateTreeAsync();
+                break;
+            
+            case SERVICE_REMOVED:
+                vmTreefilters.remove(filter);
+                doUpdateTreeAsync();
+                break;
+                
+            default:
+                logger.log(Level.WARNING, "received unknown event from VMTreeFilterRegistry: " +
+                                           actionEvent.getActionId());
+                break;
+            }
+        }
+    };
+    
+    private VMTreeDecoratorRegistry decoratorRegistry;
+    private ActionListener<ThermostatExtensionRegistry.Action> decoratorListener =
+            new ActionListener<ThermostatExtensionRegistry.Action> ()
+    {
+        public void actionPerformed(com.redhat.thermostat.common.ActionEvent<ThermostatExtensionRegistry.Action>
+                                    actionEvent)
+        {
+            ReferenceDecorator decorator = (ReferenceDecorator) actionEvent.getPayload();
+            switch (actionEvent.getActionId()) {
+            case SERVICE_ADDED:
+                vmTreeDecorators.add(decorator);
+                doUpdateTreeAsync();
+                break;
+            
+            case SERVICE_REMOVED:
+                vmTreeDecorators.remove(decorator);
+                doUpdateTreeAsync();
+                break;
+                
+            default:
+                logger.log(Level.WARNING, "received unknown event from ReferenceDecorator: " +
+                                           actionEvent.getActionId());
+                break;
+            }
+        };
+    };
     
     private boolean showHistory;
 
     private VmInformationControllerProvider vmInfoControllerProvider;
 
-    public MainWindowControllerImpl(UiFacadeFactory facadeFactory, MainView view,
-                                    BundleContext context)
+    public MainWindowControllerImpl(UiFacadeFactory facadeFactory, MainView view, RegistryFactory registryFactory)
     {
         try {
-            filterRegistry = new VMTreeFilterRegistry(context);
-            menuRegistry = new MenuRegistry(context);
+            filterRegistry = registryFactory.createVMTreeFilterRegistry();
+            decoratorRegistry = registryFactory.createVMTreeDecoratorRegistry();
+            menuRegistry = registryFactory.createMenuRegistry();
             
         } catch (InvalidSyntaxException e) {
             throw new RuntimeException(e);
         }
 
-        this.filters = new CopyOnWriteArrayList<>();
+        vmTreeDecorators = new CopyOnWriteArrayList<>();
+        
+        vmTreefilters = new CopyOnWriteArrayList<>();
         treeFilter = new TreeViewFilter();
-        filters.add(treeFilter);
+        vmTreefilters.add(treeFilter);
         
         this.facadeFactory = facadeFactory;
 
@@ -149,32 +209,11 @@
         menuRegistry.addMenuListener(menuListener);
         menuRegistry.start();
         
-        filterRegistry.addActionListener(new ActionListener<ThermostatExtensionRegistry.Action>() {
-            @Override
-            public void actionPerformed(ActionEvent<com.redhat.thermostat.client.ThermostatExtensionRegistry.Action>
-                                        actionEvent)
-            {
-                Filter filter = (Filter) actionEvent.getPayload();
-                
-                switch (actionEvent.getActionId()) {
-                case SERVICE_ADDED:
-                    filters.add(filter);
-                    doUpdateTreeAsync();
-                    break;
-                
-                case SERVICE_REMOVED:
-                    filters.remove(filter);
-                    doUpdateTreeAsync();
-                    break;
-                    
-                default:
-                    logger.log(Level.WARNING, "received unknown event from VMTreeFilterRegistry: " +
-                                               actionEvent.getActionId());
-                    break;
-                }
-            }
-        });
+        filterRegistry.addActionListener(filterListener);
         filterRegistry.start();
+        
+        decoratorRegistry.addActionListener(decoratorListener);
+        decoratorRegistry.start();
     }
 
     private class HostsVMsLoaderImpl implements HostsVMsLoader {
@@ -204,6 +243,13 @@
     
     /**
      * This method is for testing purpouse only
+     */ 
+    List<ReferenceDecorator> getVmTreeDecorators() {
+        return vmTreeDecorators;
+    }
+    
+    /**
+     * This method is for testing purpouse only
      */
     MenuRegistry.MenuListener getMenuListener() {
         return menuListener;
@@ -256,7 +302,7 @@
 
     public void doUpdateTreeAsync() {
         HostsVMsLoader loader = new HostsVMsLoaderImpl();
-        view.updateTree(filters, loader);
+        view.updateTree(vmTreefilters, vmTreeDecorators, loader);
     }
 
     private void initView(MainView mainView) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/RegistryFactory.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,60 @@
+/*
+ * 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 org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+
+class RegistryFactory {
+
+    private BundleContext context;
+    RegistryFactory(BundleContext context) {
+        this.context = context;
+    }
+    
+    VMTreeDecoratorRegistry createVMTreeDecoratorRegistry() throws InvalidSyntaxException {
+        return new VMTreeDecoratorRegistry(context);
+    }
+    
+    VMTreeFilterRegistry createVMTreeFilterRegistry() throws InvalidSyntaxException {
+        return new VMTreeFilterRegistry(context);
+    }
+    
+    MenuRegistry createMenuRegistry() throws InvalidSyntaxException {
+        return new MenuRegistry(context);
+    }
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/ThermostatExtensionRegistry.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ThermostatExtensionRegistry.java	Tue Jun 12 11:30:24 2012 +0200
@@ -54,11 +54,11 @@
     
     private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
     
-    private ServiceTracker filterTracker;
+    private ServiceTracker tracker;
     
     public ThermostatExtensionRegistry(BundleContext context, String filter, final Class<E> classType) throws InvalidSyntaxException {
                 
-        filterTracker = new ServiceTracker(context, FrameworkUtil.createFilter(filter), null) {
+        tracker = new ServiceTracker(context, FrameworkUtil.createFilter(filter), null) {
             
             @Override
             public Object addingService(ServiceReference reference) {
@@ -82,11 +82,11 @@
     }
     
     public void start() {
-        filterTracker.open();
+        tracker.open();
     }
 
     public void stop() {
-        filterTracker.close();
+        tracker.close();
     }
     
     public void addActionListener(ActionListener<Action> l) {
--- a/client/core/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Tue Jun 12 11:30:24 2012 +0200
@@ -67,7 +67,8 @@
     @Override
     public MainWindowController getMainWindow() {
         MainView mainView = new MainWindow();
-        return new MainWindowControllerImpl(this, mainView, context);
+        RegistryFactory registryFactory = new RegistryFactory(context);
+        return new MainWindowControllerImpl(this, mainView, registryFactory);
     }
 
     @Override
--- a/client/core/src/main/java/com/redhat/thermostat/client/VMTreeDecoratorRegistry.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/VMTreeDecoratorRegistry.java	Tue Jun 12 11:30:24 2012 +0200
@@ -36,6 +36,17 @@
 
 package com.redhat.thermostat.client;
 
-public class VMTreeDecoratorRegistry {
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 
+class VMTreeDecoratorRegistry extends ThermostatExtensionRegistry<ReferenceDecorator> {
+
+    private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" + ReferenceDecorator.class.getName() + "))";
+    
+    public VMTreeDecoratorRegistry(BundleContext context) throws InvalidSyntaxException {
+        super(context, FILTER, ReferenceDecorator.class);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/osgi/service/ReferenceDecorator.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,54 @@
+/*
+ * 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.client.ui.Decorator;
+
+/**
+ * This interface allows plugins to install a custom {@link Decorator} into
+ * the Reference List view.
+ * 
+ * <br /><br />
+ * 
+ * Active {@link ReferenceDecorator}s are first queried against their filters
+ * and then installed into the view if the filter passes. 
+ */
+public interface ReferenceDecorator {
+
+    Decorator getDecorator();
+    Filter getFilter();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/DecoratedDefaultMutableTreeNode.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,67 @@
+/*
+ * 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.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
+import com.redhat.thermostat.common.dao.Ref;
+
+class DecoratedDefaultMutableTreeNode  extends DefaultMutableTreeNode {
+    
+    private List<ReferenceDecorator> decorators;
+    
+    DecoratedDefaultMutableTreeNode(Ref ref) {
+        super(ref);
+        decorators = new ArrayList<>();
+    }
+    
+    public void addDecorator(ReferenceDecorator decorator) {
+        decorators.add(decorator);
+    }
+    
+    public void setDecorators(List<ReferenceDecorator> decorators) {
+        this.decorators = decorators;
+    }
+    
+    public List<ReferenceDecorator> getDecorators() {
+        return decorators;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/Decorator.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,58 @@
+/*
+ * 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.ui;
+
+
+/**
+ * A {@link Decorator} allows plugins to install special visual clues on
+ * selected components.
+ * 
+ * <br /><br />
+ * 
+ * A {@link Decorator} itself is not an entry point.
+ */
+public interface Decorator {
+
+    public static enum Quadrant {
+        TOP_LEFT,
+        BOTTOM_LEFT,
+        MAIN
+    }
+    
+    String getLabel(String originalLabel);
+    IconResource getIconResource();
+}
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/MainWindow.java	Tue Jun 12 11:30:24 2012 +0200
@@ -98,6 +98,7 @@
 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.ReferenceDecorator;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.common.ActionListener;
@@ -116,21 +117,27 @@
 
         private final DefaultTreeModel treeModel;
         private DefaultMutableTreeNode treeRoot;
+        
         private List<Filter> filters;
+        private List<ReferenceDecorator> decorators;
+        
         private HostsVMsLoader hostsVMsLoader;
 
         public BackgroundTreeModelWorker(DefaultTreeModel model, DefaultMutableTreeNode root,
-                                         List<Filter> filters, HostsVMsLoader hostsVMsLoader)
+                                         List<Filter> filters, List<ReferenceDecorator> decorators,
+                                         HostsVMsLoader hostsVMsLoader)
         {
             this.filters = filters;
             this.treeModel = model;
             this.treeRoot = root;
             this.hostsVMsLoader = hostsVMsLoader;
+            this.decorators = decorators;
         }
 
         @Override
         protected DefaultMutableTreeNode doInBackground() throws Exception {
             DefaultMutableTreeNode root = new DefaultMutableTreeNode();
+            
             Collection<HostRef> hostsInRemoteModel = hostsVMsLoader.getHosts();
             buildSubTree(root, hostsInRemoteModel);
             return root;
@@ -139,7 +146,8 @@
         private boolean buildSubTree(DefaultMutableTreeNode parent, Collection<? extends Ref> objectsInRemoteModel) {
             boolean subTreeMatches = false;
             for (Ref inRemoteModel : objectsInRemoteModel) {
-                DefaultMutableTreeNode inTreeNode = new DefaultMutableTreeNode(inRemoteModel);
+                DecoratedDefaultMutableTreeNode inTreeNode =
+                        new DecoratedDefaultMutableTreeNode(inRemoteModel);
 
                 boolean shouldInsert = false;
                 if (filters == null) {
@@ -153,7 +161,7 @@
                         }
                     }
                 }
-
+                
                 Collection<? extends Ref> children = getChildren(inRemoteModel);
                 boolean subtreeResult = buildSubTree(inTreeNode, children);
                 if (subtreeResult) {
@@ -161,6 +169,13 @@
                 }
 
                 if (shouldInsert) {
+                    for (ReferenceDecorator decorator : decorators) {
+                        Filter filter = decorator.getFilter();
+                        if (filter != null && filter.matches(inRemoteModel)) {
+                            inTreeNode.addDecorator(decorator);
+                        }
+                    }
+                    
                     parent.add(inTreeNode);
                     subTreeMatches = true;
                 }
@@ -192,9 +207,11 @@
         }
 
         private void syncTree(DefaultMutableTreeNode sourceRoot, DefaultTreeModel targetModel, DefaultMutableTreeNode targetNode) {
+            
             @SuppressWarnings("unchecked") // We know what we put into these trees.
             List<DefaultMutableTreeNode> sourceChildren = Collections.list(sourceRoot.children());
             @SuppressWarnings("unchecked")
+
             List<DefaultMutableTreeNode> targetChildren = Collections.list(targetNode.children());
             for (DefaultMutableTreeNode sourceChild : sourceChildren) {
                 Ref sourceRef = (Ref) sourceChild.getUserObject();
@@ -208,7 +225,11 @@
                 }
 
                 if (targetChild == null) {
-                    targetChild = new DefaultMutableTreeNode(sourceRef);
+                    targetChild = new DecoratedDefaultMutableTreeNode(sourceRef);
+                    if (sourceChild instanceof DecoratedDefaultMutableTreeNode) {
+                        DecoratedDefaultMutableTreeNode source = (DecoratedDefaultMutableTreeNode) sourceChild;
+                        ((DecoratedDefaultMutableTreeNode) targetChild).setDecorators(source.getDecorators());
+                    }
                     targetModel.insertNodeInto(targetChild, targetNode, targetNode.getChildCount());
                 }
 
@@ -533,8 +554,27 @@
 
         @Override
         public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
-            setToolTipText(createToolTipText(((DefaultMutableTreeNode) value).getUserObject()));
-            return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+            
+            Object node = ((DefaultMutableTreeNode) value).getUserObject();
+            setToolTipText(createToolTipText(node));
+            
+            Component component = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
+            if (value instanceof DecoratedDefaultMutableTreeNode) {
+                DecoratedDefaultMutableTreeNode treeNode = (DecoratedDefaultMutableTreeNode) value;
+                setAnnotation(treeNode, node, component);
+            }
+
+            return component;
+        }
+        
+        private void setAnnotation(DecoratedDefaultMutableTreeNode treeNode, Object value, Component component) {
+
+            List<ReferenceDecorator> decorators = treeNode.getDecorators();
+            for (ReferenceDecorator decorator : decorators) {
+                String newText = decorator.getDecorator().getLabel(getText());
+                setText(newText);
+                setLabelFor(component);
+            }
         }
         
         private String createToolTipText(Object value) {
@@ -562,20 +602,6 @@
         }
     }
 
-    private static class Separator extends JPopupMenu.Separator {
-
-        private static final long serialVersionUID = 3061771592573345826L;
-
-        @Override
-        public Dimension getPreferredSize() {
-            Dimension result = super.getPreferredSize();
-            if (result.height < 1) {
-                result.height = 5;
-            }
-            return result;
-        }
-    }
-
     @Override
     public void addActionListener(ActionListener<Action> l) {
         actionNotifier.addActionListener(l);
@@ -594,8 +620,10 @@
     }
     
     @Override
-    public void updateTree(List<Filter> filters, HostsVMsLoader hostsVMsLoader) {
-        BackgroundTreeModelWorker worker = new BackgroundTreeModelWorker(publishedTreeModel, publishedRoot, filters, hostsVMsLoader);
+    public void updateTree(List<Filter> filters, List<ReferenceDecorator> decorators, HostsVMsLoader hostsVMsLoader) {
+        BackgroundTreeModelWorker worker =
+                new BackgroundTreeModelWorker(publishedTreeModel, publishedRoot,
+                                              filters, decorators, hostsVMsLoader);
         worker.execute();
     }
 
--- a/client/core/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/test/java/com/redhat/thermostat/client/MainWindowControllerImplTest.java	Tue Jun 12 11:30:24 2012 +0200
@@ -64,9 +64,11 @@
 import org.mockito.ArgumentCaptor;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.util.tracker.ServiceTracker;
 
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 import com.redhat.thermostat.client.osgi.service.VMContextAction;
 import com.redhat.thermostat.client.ui.SummaryController;
 import com.redhat.thermostat.client.ui.SummaryView;
@@ -105,6 +107,13 @@
     private VMContextAction action1;
     private VMContextAction action2;
 
+    private VMTreeFilterRegistry filters;
+    private VMTreeDecoratorRegistry decorators;
+    private MenuRegistry menues;
+    
+    private ActionListener<ThermostatExtensionRegistry.Action> filtersListener;
+    private ActionListener<ThermostatExtensionRegistry.Action> decoratorsListener;
+    
     @BeforeClass
     public static void setUpOnce() {
         // TODO remove when controller uses mocked objects rather than real swing objects
@@ -133,9 +142,22 @@
         view = mock(MainView.class);
         ArgumentCaptor<ActionListener> grabListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(view).addActionListener(grabListener.capture());
+        
+        RegistryFactory registryFactory = mock(RegistryFactory.class);
+        filters = mock(VMTreeFilterRegistry.class);
+        decorators = mock(VMTreeDecoratorRegistry.class);
+        menues = mock(MenuRegistry.class);
 
-        BundleContext registry = mock(BundleContext.class);
+        when(registryFactory.createMenuRegistry()).thenReturn(menues);
+        when(registryFactory.createVMTreeDecoratorRegistry()).thenReturn(decorators);
+        when(registryFactory.createVMTreeFilterRegistry()).thenReturn(filters);
+        
+        ArgumentCaptor<ActionListener> grabFiltersListener = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(filters).addActionListener(grabFiltersListener.capture());
 
+        ArgumentCaptor<ActionListener> grabDecoratorsListener = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(decorators).addActionListener(grabDecoratorsListener.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);
@@ -144,8 +166,11 @@
 
         setUpVMContextActions();
 
-        controller = new MainWindowControllerImpl(uiFacadeFactory, view, registry);
+        controller = new MainWindowControllerImpl(uiFacadeFactory, view, registryFactory);
         l = grabListener.getValue();
+        
+        filtersListener = grabFiltersListener.getValue();
+        decoratorsListener = grabDecoratorsListener.getValue();
     }
 
     private void setUpVMContextActions() {
@@ -194,12 +219,33 @@
     }
 
     @Test
+    public void verifyDecoratorsAdded() {
+
+        List<ReferenceDecorator> currentDecoratros = controller.getVmTreeDecorators();
+        assertEquals(0, currentDecoratros.size());
+        
+        ActionEvent<ThermostatExtensionRegistry.Action> event =
+                new ActionEvent<ThermostatExtensionRegistry.Action>(decorators,
+                        ThermostatExtensionRegistry.Action.SERVICE_ADDED);
+        
+        ReferenceDecorator payload = mock(ReferenceDecorator.class);
+        event.setPayload(payload);
+        
+        decoratorsListener.actionPerformed(event);
+
+        currentDecoratros = controller.getVmTreeDecorators();
+        assertEquals(1, currentDecoratros.size());
+        assertEquals(payload, currentDecoratros.get(0));
+        
+        verify(view).updateTree(any(List.class), any(List.class), any(HostsVMsLoader.class));
+    }
+    
+    @Test
     public void verifyThatHiddenEventStopsController() {
 
         l.actionPerformed(new ActionEvent<MainView.Action>(view, MainView.Action.HIDDEN));
 
         verify(mainWindowTimer).stop();
-
     }
 
     @Test
@@ -209,9 +255,9 @@
 
         l.actionPerformed(new ActionEvent<MainView.Action>(view, MainView.Action.HOST_VM_TREE_FILTER));
 
-        verify(view).updateTree(any(List.class), any(HostsVMsLoader.class));
+        verify(view).updateTree(any(List.class), any(List.class), any(HostsVMsLoader.class));
     }
-
+    
     @Test
     public void verifyTimerGetsStartedOnBecomingVisible() {
         l.actionPerformed(new ActionEvent<MainView.Action>(view, MainView.Action.VISIBLE));
@@ -245,7 +291,7 @@
         controller.doUpdateTreeAsync();
 
         ArgumentCaptor<HostsVMsLoader> arg = ArgumentCaptor.forClass(HostsVMsLoader.class);
-        verify(view).updateTree(any(List.class), arg.capture());
+        verify(view).updateTree(any(List.class), any(List.class), arg.capture());
         HostsVMsLoader loader = arg.getValue();
 
         Collection<HostRef> actualHosts = loader.getHosts();
@@ -269,7 +315,7 @@
         controller.doUpdateTreeAsync();
 
         ArgumentCaptor<HostsVMsLoader> arg = ArgumentCaptor.forClass(HostsVMsLoader.class);
-        verify(view).updateTree(any(List.class), arg.capture());
+        verify(view).updateTree(any(List.class), any(List.class), arg.capture());
         HostsVMsLoader loader = arg.getValue();
 
         Collection<HostRef> actualHosts = loader.getHosts();
@@ -294,7 +340,7 @@
         controller.doUpdateTreeAsync();
 
         ArgumentCaptor<HostsVMsLoader> arg = ArgumentCaptor.forClass(HostsVMsLoader.class);
-        verify(view).updateTree(any(List.class), arg.capture());
+        verify(view).updateTree(any(List.class), any(List.class), arg.capture());
         HostsVMsLoader loader = arg.getValue();
 
         Collection<VmRef> actualVMs = loader.getVMs(host);
--- a/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/core/src/test/java/com/redhat/thermostat/client/ui/MainWindowTest.java	Tue Jun 12 11:30:24 2012 +0200
@@ -39,15 +39,18 @@
 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 static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
-import javax.swing.AbstractAction;
 import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JRadioButtonMenuItem;
 
@@ -69,11 +72,15 @@
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
 
+import com.redhat.thermostat.client.HostsVMsLoader;
 import com.redhat.thermostat.client.MainView;
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.Ref;
 
 @RunWith(CacioFESTRunner.class)
 public class MainWindowTest {
@@ -124,6 +131,40 @@
 
     @Category(GUITest.class)
     @Test
+    public void testHostVmDecoratorsAdded() {
+        
+        List<ReferenceDecorator> decorators = new ArrayList<>();
+        ReferenceDecorator refDecorator = mock(ReferenceDecorator.class);
+        final Decorator decorator = mock(Decorator.class);
+        when(decorator.getLabel(anyString())).thenReturn("fluff");
+        
+        when(refDecorator.getDecorator()).thenReturn(decorator);
+        
+        Filter filter = mock(Filter.class);
+        when(filter.matches(any(Ref.class))).thenReturn(false).thenReturn(true);
+
+        when(refDecorator.getFilter()).thenReturn(filter);
+        
+        decorators.add(refDecorator);
+        
+        HostsVMsLoader hostsVMsLoader = mock(HostsVMsLoader.class);
+        Collection<HostRef> expectedHosts = new ArrayList<>();
+        expectedHosts.add(new HostRef("123", "fluffhost1"));
+        expectedHosts.add(new HostRef("456", "fluffhost2"));
+        
+        when(hostsVMsLoader.getHosts()).thenReturn(expectedHosts);
+        
+        window.updateTree(null, decorators, hostsVMsLoader);
+
+        frameFixture.show();
+        frameFixture.requireVisible();
+        
+        verify(decorator, times(0)).getLabel("fluffhost1");
+        verify(decorator, atLeastOnce()).getLabel("fluffhost2");
+    }
+    
+    @Category(GUITest.class)
+    @Test
     public void testHostVMTreeFilterPropertySupport() {
         frameFixture.show();
         JTextComponentFixture hostVMTreeFilterField = frameFixture.textBox("hostVMTreeFilter");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/DeadVMDecorator.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,93 @@
+/*
+ * 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.filter.vm;
+
+import com.redhat.thermostat.client.osgi.service.Filter;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.client.ui.IconResource;
+import com.redhat.thermostat.common.dao.DAOFactory;
+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;
+
+public class DeadVMDecorator implements ReferenceDecorator {
+    
+    private class VMDecorator implements Decorator {
+        @Override
+        public IconResource getIconResource() {
+            return null;
+        }
+        
+        @Override
+        public String getLabel(String originalLabel) {
+            return "[not running] " + originalLabel;
+        }
+    }
+
+    private Filter decoratorFilter;
+    private VMDecorator decorator;
+    
+    public DeadVMDecorator(final DAOFactory dao) {
+        decorator = new VMDecorator();
+        decoratorFilter = new Filter() {            
+            @Override
+            public boolean matches(Ref ref) {
+                if (ref instanceof VmRef) {
+                    VmRef vm = (VmRef) ref;
+                    
+                    VmInfoDAO info = dao.getVmInfoDAO();
+                    VmInfo vmInfo = info.getVmInfo(vm);
+                    
+                    return !vmInfo.isAlive();
+                }
+                return false;
+            }
+        };
+    }
+    
+    @Override
+    public Decorator getDecorator() {
+        return decorator;
+    }
+    
+    @Override
+    public Filter getFilter() {
+        return decoratorFilter;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMDecorator.java	Tue Jun 12 11:30:24 2012 +0200
@@ -0,0 +1,76 @@
+/*
+ * 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.filter.vm;
+
+import com.redhat.thermostat.client.osgi.service.Filter;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.client.ui.IconResource;
+import com.redhat.thermostat.common.dao.DAOFactory;
+
+public class VMDecorator implements ReferenceDecorator {
+    
+    private class LivingVMDecorator implements Decorator {
+        @Override
+        public IconResource getIconResource() {
+            return null;
+        }
+        
+        @Override
+        public String getLabel(String originalLabel) {
+            return originalLabel;
+        }
+    }
+
+    private LivingVMFilter decoratorFilter;
+    private LivingVMDecorator decorator;
+    
+    public VMDecorator(final DAOFactory dao) {
+        decorator = new LivingVMDecorator();
+        decoratorFilter = new LivingVMFilter(dao);
+    }
+    
+    @Override
+    public Decorator getDecorator() {
+        return decorator;
+    }
+    
+    @Override
+    public Filter getFilter() {
+        return decoratorFilter;
+    }
+}
--- a/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMFilterActivator.java	Tue Jun 12 11:30:24 2012 +0200
+++ b/client/living-vm-filter/src/main/java/com/redhat/thermostat/client/filter/vm/VMFilterActivator.java	Tue Jun 12 11:30:24 2012 +0200
@@ -46,6 +46,7 @@
 import com.redhat.thermostat.client.osgi.service.ApplicationService;
 import com.redhat.thermostat.client.osgi.service.Filter;
 import com.redhat.thermostat.client.osgi.service.MenuAction;
+import com.redhat.thermostat.client.osgi.service.ReferenceDecorator;
 
 public class VMFilterActivator implements BundleActivator {
     
@@ -59,11 +60,19 @@
                 props.put(MenuAction.PARENT_MENU, LivingVMFilterMenuAction.PARENT_MENU);
                 
                 ApplicationService service = (ApplicationService) context.getService(reference);
+                
                 LivingVMFilter filter = new LivingVMFilter(service.getDAOFactory());
+                VMDecorator decorator = new VMDecorator(service.getDAOFactory());
+                DeadVMDecorator deadDecorator = new DeadVMDecorator(service.getDAOFactory());
+                
                 LivingVMFilterMenuAction menu = new LivingVMFilterMenuAction(filter);
                 
+                context.registerService(ReferenceDecorator.class.getName(), deadDecorator, null);
+                context.registerService(ReferenceDecorator.class.getName(), decorator, null);
+                
                 context.registerService(Filter.class.getName(), filter, null);
                 context.registerService(MenuAction.class.getName(), menu, props);
+                
                 return super.addingService(reference);
             }
         };