changeset 2171:75072a8750ce ipc

Merge upstream changes.
author Elliott Baron <ebaron@redhat.com>
date Tue, 09 Feb 2016 12:39:50 -0500
parents 7db6f58cb5bc (current diff) c55a032a5f86 (diff)
children 03f497feb8b4 de14ec54e44a
files agent/pom.xml client/core/src/main/java/com/redhat/thermostat/client/core/experimental/Duration.java client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/host/swing/HostInfoLabelDecorator.java client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMLabelDecorator.java client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/host/swing/HostInfoLabelDecoratorTest.java client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/VMInformationRegistry.java client/swing/src/main/resources/fontawesome-webfont.ttf dev/pom.xml distribution/assembly/core-assembly.xml distribution/pom.xml notes/client-swing/src/main/java/com/redhat/thermostat/notes/client/swing/internal/HostNotesProvider.java notes/client-swing/src/main/java/com/redhat/thermostat/notes/client/swing/internal/VmNotesProvider.java pom.xml thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementBeanAdapterRegistrationTest.java unix-process-handler/src/main/java/com/redhat/thermostat/service/activator/Activator.java unix-process-handler/src/main/java/com/redhat/thermostat/service/process/UnixProcessUtilities.java unix-process-handler/src/test/java/com/redhat/thermostat/service/process/UnixProcessUtilitiesTest.java vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/model/MemoryStatTest.java vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectCommandHelper.java vm-io/common/src/test/java/com/redhat/thermostat/vm/io/common/internal/VmCpuStatDAOImplStatementDescriptorRegistrationTest.java
diffstat 737 files changed, 33757 insertions(+), 4037 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Mon Feb 08 18:37:19 2016 -0500
+++ b/.hgtags	Tue Feb 09 12:39:50 2016 -0500
@@ -18,3 +18,4 @@
 648920f5c38292e9b04e1de4c84c60084d88a833 1.99.1-SNAPSHOT
 7ece95c1a18e5d96172a252c29aacada6d3c875e 1.99.3-SNAPSHOT
 34dec6b63ecc51ff0a915076246ff5e17005dd0c 1.99.5-SNAPSHOT
+c4a7d29eccfa811c05796e9908565e05aa322e5a 1.99.7-SNAPSHOT
Binary file FontAwesomeCheatsheet.pdf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.fontawesome	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,6 @@
+Font awesome location:
+./client/swing/src/main/resources/fontawesome-webfont.ttf
+
+Last update with version 4.5.0, downloaded on Tue, Janauary 12, from https://fortawesome.github.io/Font-Awesome
+
+Please, refer to FontAwesomeCheatsheet.pdf for the updated list of icons and IDs.
--- a/agent/cli/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/cli/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent-cli</artifactId>
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Tue Feb 09 12:39:50 2016 -0500
@@ -89,7 +89,10 @@
     // when you change those!
     private static final String VERBOSE_MODE_AGENT_STOPPED_MSG = "Agent stopped.";
     private static final String VERBOSE_MODE_AGENT_STARTED_MSG = "Agent started.";
-    
+
+    private static final String SIGINT_NAME = "INT";
+    private static final String SIGTERM_NAME = "TERM";
+
     private static final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
     
     private final BundleContext bundleContext;
@@ -105,6 +108,8 @@
     private final WriterID writerId;
     private CountDownLatch shutdownLatch;
 
+    private CustomSignalHandler handler;
+
     public AgentApplication(BundleContext bundleContext, ExitStatus exitStatus, WriterID writerId) {
         this(bundleContext, exitStatus, writerId, new ConfigurationCreator(), new DbServiceFactory());
     }
@@ -203,6 +208,8 @@
             shutdownLatch.await();
             logger.fine("terminating agent cmd");
         } catch (InterruptedException e) {
+            // Ensure proper shutdown if interrupted
+            handler.handle(new Signal(SIGINT_NAME));
             return;
         }
     }
@@ -323,9 +330,9 @@
                         .get(BackendInfoDAO.class.getName());
 
                 Agent agent = startAgent(storage, agentInfoDAO, backendInfoDAO);
-                SignalHandler handler = new CustomSignalHandler(agent, configServer);
-                Signal.handle(new Signal("INT"), handler);
-                Signal.handle(new Signal("TERM"), handler);
+                handler = new CustomSignalHandler(agent, configServer);
+                Signal.handle(new Signal(SIGINT_NAME), handler);
+                Signal.handle(new Signal(SIGTERM_NAME), handler);
             }
 
             @Override
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Tue Feb 09 12:39:50 2016 -0500
@@ -48,7 +48,6 @@
 import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.cli.AbstractCommand;
 import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
@@ -62,7 +61,7 @@
  * Simple service that allows starting Agent and DB Backend
  * in a single step.
  */
-public class ServiceCommand extends AbstractCommand implements ActionListener<ApplicationState> {
+public class ServiceCommand extends AbstractStateNotifyingCommand implements ActionListener<ApplicationState> {
     
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
     private static final Logger logger = LoggingUtils.getLogger(ServiceCommand.class);
@@ -72,6 +71,7 @@
     private BundleContext context;
     private Launcher launcher;
     private boolean storageFailed = false;
+    private boolean agentStarted = false;
     private CommandContext cmdCtx;
 
     public ServiceCommand(BundleContext context) {
@@ -93,12 +93,17 @@
         if (storageFailed) {
             storageFailed = false;
             context.ungetService(launcherRef);
+            getNotifier().fireAction(ApplicationState.FAIL);
             throw new CommandException(translator.localize(LocaleResources.SERVICE_FAILED_TO_START_DB));
         }
         
         String[] storageStopArgs = new String[] { "storage", "--stop" };
         launcher.run(storageStopArgs, false);
-        
+
+        if (agentStarted) {
+            getNotifier().fireAction(ApplicationState.STOP);
+        }
+
         context.ungetService(launcherRef);
         cmdCtx = null;
     }
@@ -118,6 +123,7 @@
                     // Payload is connection URL
                     Object payload = actionEvent.getPayload();
                     if (payload == null || !(payload instanceof String)) {
+                        getNotifier().fireAction(ApplicationState.FAIL);
                         throw new CommandException(translator.localize(LocaleResources.UNEXPECTED_RESULT_STORAGE));
                     }
                     String dbUrl = (String) payload;
@@ -132,6 +138,7 @@
                     // Payload is exception
                     payload = actionEvent.getPayload();
                     if (payload == null || !(payload instanceof Exception)) {
+                        getNotifier().fireAction(ApplicationState.FAIL);
                         throw new CommandException(translator.localize(LocaleResources.UNEXPECTED_RESULT_STORAGE));
                     }
                     Exception ex = (Exception) payload;
@@ -152,7 +159,7 @@
         return false;
     }
     
-    private static class AgentStartedListener implements ActionListener<ApplicationState> {
+    private class AgentStartedListener implements ActionListener<ApplicationState> {
 
         private final Console console;
 
@@ -169,16 +176,22 @@
                 // notified in the case that the command is invoked by some other means later.
                 agent.getNotifier().removeActionListener(this);
 
-                switch (actionEvent.getActionId()) {
+                ApplicationState state = actionEvent.getActionId();
+                // propagate the Agent ActionEvent if START or FAIL
+                switch (state) {
                 case START:
+                    agentStarted = true;
                     logger.fine("Agent started via service. Agent ID was: " + actionEvent.getPayload());
+                    getNotifier().fireAction(ApplicationState.START, actionEvent.getPayload());
                     break;
                 case FAIL:
                     console.getError().println(translator.localize(LocaleResources.STARTING_AGENT_FAILED).getContents());
+                    getNotifier().fireAction(ApplicationState.FAIL, actionEvent.getPayload());
                     break;
+                default:
+                    throw new AssertionError("Unexpected state " + state);
                 }
             }
-
         }
     }
 
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -81,7 +81,8 @@
     private static final String[] STORAGE_START_ARGS = { "storage", "--start" };
     private static final String[] STORAGE_STOP_ARGS = { "storage", "--stop" };
     private static final String[] AGENT_ARGS = {"agent", "-d", "Test String"};
-    
+    private static final String AGENT_ID = "Test ID";
+
     @SuppressWarnings("unchecked")
     @Before
     public void setUp() {
@@ -116,7 +117,7 @@
 
     @SuppressWarnings("unchecked")
     @Test(timeout=1000)
-    public void testRunOnce() throws CommandException {
+    public void testRunOnce() throws CommandException, InterruptedException {
         doAnswer(new Answer<Void>() {
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
@@ -130,7 +131,43 @@
                 return null;
             }
         }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-        
+
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                listeners = (Collection<ActionListener<ApplicationState>>)args[1];
+
+                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
+                when(mockActionEvent.getPayload()).thenReturn(AGENT_ID);
+
+                for(ActionListener<ApplicationState> listener : listeners) {
+                    listener.actionPerformed(mockActionEvent);
+                }
+                return null;
+            }
+        }).when(mockLauncher).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
+
+        final boolean[] result = new boolean[2];
+        final String[] agentIdFound = new String[1];
+        serviceCommand.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case FAIL:
+                        result[0] = false;
+                        break;
+                    case START:
+                        result[0] = true;
+                        agentIdFound[0] = (String) actionEvent.getPayload();
+                        break;
+                    case STOP:
+                        result[1] = true;
+                        break;
+                }
+            }
+        });
+
         boolean exTriggered = false;
         try {
             serviceCommand.run(mockCommandContext);
@@ -138,16 +175,72 @@
             exTriggered = true;
         }
         Assert.assertFalse(exTriggered);
-        
+        Assert.assertTrue("Agent expected to fire START event", result[0]);
+        Assert.assertTrue("Agent expected to fire STOP event", result[1]);
+        Assert.assertEquals("Payload does not contain AgentId matching the agent started", agentIdFound[0], AGENT_ID);
+
         verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
         verify(mockLauncher, times(1)).run(eq(STORAGE_STOP_ARGS), anyBoolean());
         verify(mockLauncher, times(1)).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
+        verify(mockActionEvent, times(2)).getActionId();
+    }
+
+    @Test(timeout=1000)
+    public void testStorageStartUnknownPath()  throws CommandException {
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                listeners = (Collection<ActionListener<ApplicationState>>)args[1];
+
+                when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
+                // Return a null payload in order to trigger unknown path
+                when(mockActionEvent.getPayload()).thenReturn(null);
+
+                for(ActionListener<ApplicationState> listener : listeners) {
+                    listener.actionPerformed(mockActionEvent);
+                }
+                return null;
+            }
+        }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+
+        final boolean[] result = new boolean[1];
+        serviceCommand.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case FAIL:
+                        result[0] = true;
+                        break;
+                    case START:
+                        result[0] = false;
+                        break;
+                    case STOP:
+                        result[0] = false;
+                        break;
+                }
+            }
+        });
+
+        boolean exTriggered = false;
+        try {
+            serviceCommand.run(mockCommandContext);
+        } catch (CommandException e) {
+            exTriggered = true;
+        }
+        Assert.assertFalse(exTriggered);
+        Assert.assertEquals("Unexpected result from storage.\n", stdErrOut.toString());
+        Assert.assertTrue("Agent expected to fire FAIL event", result[0]);
+
+        verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
+        verify(mockLauncher, times(1)).run(eq(STORAGE_STOP_ARGS), anyBoolean());
+        verify(mockLauncher, never()).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
         verify(mockActionEvent, times(1)).getActionId();
     }
 
     @SuppressWarnings("unchecked")
     @Test(timeout=1000)
-    public void testStorageFailStart()  throws CommandException {
+    public void testStorageFailStart() throws CommandException, InterruptedException {
         doAnswer(new Answer<Void>() {
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
@@ -162,7 +255,26 @@
                 return null;
             }
         }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-        
+
+        final boolean[] result = new boolean[1];
+        serviceCommand.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case FAIL:
+                        result[0] = true;
+                        break;
+                    case START:
+                        result[0] = false;
+                        break;
+                    case STOP:
+                        result[0] = false;
+                        break;
+                }
+            }
+        });
+
         boolean exTriggered = false;
         try {
             serviceCommand.run(mockCommandContext);
@@ -171,31 +283,51 @@
         }
         Assert.assertTrue(exTriggered);
         Assert.assertEquals("Test Exception\n", stdErrOut.toString());
-        
+        Assert.assertTrue("Agent expected to fire FAIL event", result[0]);
+
         verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
         verify(mockLauncher, never()).run(eq(STORAGE_STOP_ARGS), anyBoolean());
         verify(mockLauncher, never()).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
         verify(mockActionEvent, times(1)).getActionId();
     }
-    
+
     @Test(timeout=1000)
-    public void testStorageFailStartUnknown()  throws CommandException {
+    public void testStorageFailStartUnknownPath()  throws CommandException {
         doAnswer(new Answer<Void>() {
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
                 listeners = (Collection<ActionListener<ApplicationState>>)args[1];
-                
+
                 when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
                 // Return a null payload in order to trigger unknown path
                 when(mockActionEvent.getPayload()).thenReturn(null);
-                
+
                 for(ActionListener<ApplicationState> listener : listeners) {
                     listener.actionPerformed(mockActionEvent);
                 }
                 return null;
             }
         }).when(mockLauncher).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
-        
+
+        final boolean[] result = new boolean[1];
+        serviceCommand.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case FAIL:
+                        result[0] = true;
+                        break;
+                    case START:
+                        result[0] = false;
+                        break;
+                    case STOP:
+                        result[0] = false;
+                        break;
+                }
+            }
+        });
+
         boolean exTriggered = false;
         try {
             serviceCommand.run(mockCommandContext);
@@ -204,7 +336,8 @@
         }
         Assert.assertTrue(exTriggered);
         Assert.assertEquals("Unexpected result from storage.\n", stdErrOut.toString());
-        
+        Assert.assertTrue("Agent expected to fire FAIL event", result[0]);
+
         verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
         verify(mockLauncher, never()).run(eq(STORAGE_STOP_ARGS), anyBoolean());
         verify(mockLauncher, never()).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
@@ -217,9 +350,9 @@
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
                 listeners = (Collection<ActionListener<ApplicationState>>)args[1];
-                
+
                 when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
-                
+
                 for(ActionListener<ApplicationState> listener : listeners) {
                     listener.actionPerformed(mockActionEvent);
                 }
@@ -230,16 +363,35 @@
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
                 listeners = (Collection<ActionListener<ApplicationState>>)args[1];
-                
+
                 when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
-                
+
                 for(ActionListener<ApplicationState> listener : listeners) {
                     listener.actionPerformed(mockActionEvent);
                 }
                 return null;
             }
         }).when(mockLauncher).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
-        
+
+        final boolean[] result = new boolean[1];
+        serviceCommand.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case FAIL:
+                        result[0] = true;
+                        break;
+                    case START:
+                        result[0] = false;
+                        break;
+                    case STOP:
+                        result[0] = false;
+                        break;
+                }
+            }
+        });
+
         boolean exTriggered = false;
         try {
             serviceCommand.run(mockCommandContext);
@@ -248,7 +400,8 @@
         }
         Assert.assertFalse(exTriggered);
         Assert.assertEquals("Thermostat agent failed to start. See logs for details.\n", stdErrOut.toString());
-        
+        Assert.assertTrue("Agent expected to fire FAIL event", result[0]);
+
         verify(mockLauncher, times(1)).run(eq(STORAGE_START_ARGS), isA(Collection.class), anyBoolean());
         verify(mockLauncher, times(1)).run(eq(STORAGE_STOP_ARGS), anyBoolean());
         verify(mockLauncher, times(1)).run(eq(AGENT_ARGS), isA(Collection.class), anyBoolean());
--- a/agent/command-server/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/command-server/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent-command-server</artifactId>
--- a/agent/command/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/command/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent-command</artifactId>
--- a/agent/core/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/core/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent-core</artifactId>
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/utils/SysConf.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/utils/SysConf.java	Tue Feb 09 12:39:50 2016 -0500
@@ -61,27 +61,17 @@
     }
 
     private static String sysConf(String arg) {
-        BufferedReader reader = null;
         try {
             Process process = Runtime.getRuntime().exec(new String[] { "getconf", arg });
             int result = process.waitFor();
             if (result != 0) {
                 return null;
             }
-            reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
-            return reader.readLine();
-        } catch (IOException e) {
-            return null;
-        } catch (InterruptedException e) {
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+                return reader.readLine();
+            }
+        } catch (IOException | InterruptedException e) {
             return null;
-        } finally {
-            if (reader != null) {
-                try {
-                    reader.close();
-                } catch (IOException e) {
-                    // TODO
-                }
-            }
         }
     }
 }
--- a/agent/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent</artifactId>
--- a/agent/proxy/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/proxy/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-agent-proxy</artifactId>
--- a/agent/proxy/server/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/proxy/server/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-agent-proxy</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-agent-proxy-server</artifactId>
--- a/agent/proxy/server/src/main/java/com/redhat/thermostat/agent/proxy/server/AgentProxyControlImpl.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/proxy/server/src/main/java/com/redhat/thermostat/agent/proxy/server/AgentProxyControlImpl.java	Tue Feb 09 12:39:50 2016 -0500
@@ -39,7 +39,10 @@
 import java.io.File;
 import java.io.IOException;
 import java.rmi.RemoteException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Properties;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.common.utils.LoggingUtils;
@@ -51,9 +54,10 @@
 class AgentProxyControlImpl {
     
     private static final Logger logger = LoggingUtils.getLogger(AgentProxyControlImpl.class);
+    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
+    private static final String JCMD_NAME = "jcmd";
+    private static final String JCMD_MANAGEMENT_AGENT_START_LOCAL = "ManagementAgent.start_local";
 
-    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
-    
     private final int pid;
     private final VirtualMachineUtils vmUtils;
     
@@ -81,11 +85,8 @@
             String agent = null;
             try {
                 props = vm.getSystemProperties();
-                home = props.getProperty("java.home");
-                agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
-                logger.fine("Loading '" + agent + "' into VM (pid: " + pid + ")");
-                vm.loadAgent(agent);
-
+                startManagementAgent(props);
+                logger.fine("Started management agent for vm '" + pid + "'");
                 props = vm.getAgentProperties();
                 connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
             } catch (IOException | AgentLoadException | AgentInitializationException e) {
@@ -94,6 +95,52 @@
         }
     }
 
+    private void startManagementAgent(Properties props) throws IOException,
+                                                               AgentLoadException,
+                                                               AgentInitializationException {
+        String home = props.getProperty("java.home");
+        try {
+            // JDK 7 and up have jcmd with JMX management agent start support.
+            startManagementAgentUsingJcmd(home, pid);
+        } catch (Exception e) {
+            // Fall back to old management-agent.jar behaviour.
+            logger.log(Level.FINE, "Failed to activate JMX agent via jcmd.", e);
+            startManagementAgentUsingJavaAgent(home);
+        }
+    }
+
+    private void startManagementAgentUsingJcmd(String home, int vmPid) throws IOException, InterruptedException {
+        String jcmd = home + File.separator + "bin" + File.separator + JCMD_NAME;
+        File jcmdFile = new File(jcmd);
+        if (!jcmdFile.exists()) {
+            // java.home might be JRE home. Try one level up.
+            File binDir = new File(new File(home).getParentFile(), "bin");
+            jcmd = binDir.getAbsolutePath() + File.separator + JCMD_NAME;
+        }
+        String[] args = new String[] { jcmd,
+                                       Integer.toString(vmPid),
+                                       JCMD_MANAGEMENT_AGENT_START_LOCAL };
+        List<String> jcmdArgs = Arrays.asList(args);
+        logger.fine("Starting JMX management agent via JCMD: " + jcmdArgs);
+        Process process = startProcess(jcmdArgs);
+        int result = process.waitFor();
+        if (result != 0) {
+            throw new IllegalStateException("Failed to execute jcmd. Exit code was: " + result);
+        }
+    }
+
+    // Package private for testing
+    Process startProcess(List<String> jcmdArgs) throws IOException {
+        ProcessBuilder builder = new ProcessBuilder(jcmdArgs);
+        return builder.start();
+    }
+
+    private void startManagementAgentUsingJavaAgent(String home) throws AgentLoadException, AgentInitializationException, IOException {
+        String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
+        logger.fine("Loading '" + agent + "' into VM (pid: " + pid + ")");
+        vm.loadAgent(agent);
+    }
+
     boolean isAttached() {
         return attached;
     }
--- a/agent/proxy/server/src/test/java/com/redhat/thermostat/agent/proxy/server/AgentProxyControlImplTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/agent/proxy/server/src/test/java/com/redhat/thermostat/agent/proxy/server/AgentProxyControlImplTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -38,6 +38,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -48,6 +49,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Properties;
 
 import org.junit.Before;
@@ -58,6 +61,9 @@
 
 public class AgentProxyControlImplTest {
     
+    private static final int SUCCESS = 0;
+    private static final int FAILURE = 3;
+    private static final int VM_PID = 0;
     private AgentProxyControlImpl control;
     private VirtualMachine vm;
     private VirtualMachineUtils vmUtils;
@@ -77,14 +83,52 @@
         when(vm.getSystemProperties()).thenReturn(sysProps);
         
         when(vmUtils.attach(anyString())).thenReturn(vm);
-        control = new AgentProxyControlImpl(0, vmUtils);
+        control = new AgentProxyControlImpl(VM_PID, vmUtils);
     }
     
     @Test
-    public void testAttach() throws Exception {
-        control.attach();
+    public void testAttachJcmd() throws Exception {
+        final Process process = mock(Process.class);
+        when(process.waitFor()).thenReturn(SUCCESS);
+        @SuppressWarnings("unchecked")
+        final List<String>[] startProcessArgs = new List[1];
+        AgentProxyControlImpl controlUnderTest = new AgentProxyControlImpl(VM_PID, vmUtils) {
+            @Override
+            Process startProcess(List<String> args) {
+                startProcessArgs[0] = args;
+                return process;
+            }
+        };
+        controlUnderTest.attach();
+        List<String> args = startProcessArgs[0];
+        assertNotNull(args);
+        String[] expectedArgs = new String[] {
+                "/path/to/java/bin/jcmd", // Uses the jrePath.getParentFile() path
+                Integer.toString(VM_PID),
+                "ManagementAgent.start_local"
+        };
+        List<String> expectedList = Arrays.asList(expectedArgs);
+        assertEquals(expectedList, args);
         
-        verify(vmUtils).attach("0");
+        verify(vmUtils).attach(Integer.toString(VM_PID));
+        verify(vm, times(2)).getAgentProperties();
+        verify(vm).getSystemProperties();
+        verify(vm, times(0)).loadAgent("/path/to/java/home" + File.separator + "lib" + File.separator + "management-agent.jar");
+    }
+    
+    @Test
+    public void testAttachManagementJar() throws Exception {
+        final Process process = mock(Process.class);
+        when(process.waitFor()).thenReturn(FAILURE);
+        AgentProxyControlImpl controlUnderTest = new AgentProxyControlImpl(VM_PID, vmUtils) {
+            @Override
+            Process startProcess(List<String> args) {
+                return process; // pretending jcmd process start fails
+            }
+        };
+        controlUnderTest.attach();
+        
+        verify(vmUtils).attach(Integer.toString(VM_PID));
         verify(vm, times(2)).getAgentProperties();
         verify(vm).getSystemProperties();
         verify(vm).loadAgent("/path/to/java/home" + File.separator + "lib" + File.separator + "management-agent.jar");
--- a/annotations/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/annotations/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-annotations</artifactId>
--- a/assembly/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/assembly/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-assembly</artifactId>
--- a/client/cli/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/cli/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-client</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-client-cli</artifactId>
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VmStatCommandTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/VmStatCommandTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -211,6 +211,7 @@
         doReturn(Arrays.asList(data[0])).when(delegates[0]).getStatRow(eq(stat));
         doReturn(Arrays.asList(data[1])).when(delegates[1]).getStatRow(eq(stat));
 
+        final Throwable[] exception = new Throwable[1];
         Thread t = new Thread() {
             public void run() {
                 SimpleArguments args = setupArguments();
@@ -218,8 +219,7 @@
                 try {
                     cmd.run(cmdCtxFactory.createContext(args));
                 } catch (CommandException e) {
-                    // TODO Auto-generated catch block
-                    e.printStackTrace();
+                    exception[0] = e;
                 }
             }
         };
@@ -255,6 +255,7 @@
             return;
         }
         assertFalse(timerFactory.isActive());
+        assertEquals(null, exception[0]); // no exception
     }
 
     @Test
--- a/client/command/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/command/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-client</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-client-command</artifactId>
--- a/client/core/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/core/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-client</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-client-core</artifactId>
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/Duration.java	Mon Feb 08 18:37:19 2016 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * Copyright 2012-2015 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.core.experimental;
-
-import java.util.concurrent.TimeUnit;
-
-public class Duration {
-    public final int value;
-    public final TimeUnit unit;
-
-    public Duration(int value, TimeUnit unit) {
-        this.value = value;
-        this.unit = unit;
-    }
-
-    @Override
-    public String toString() {
-        return value + " " + unit;
-    }
-}
\ No newline at end of file
--- a/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeController.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/core/src/main/java/com/redhat/thermostat/client/core/experimental/TimeRangeController.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.core.experimental;
 
+import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.common.model.Range;
 
 import java.util.List;
@@ -55,7 +56,7 @@
 
     public void update(Duration userDesiredDuration, Range<Long> newAvailableRange, StatsSupplier<T, R> dao, R ref, SingleArgRunnable<T> updater) {
         long now = System.currentTimeMillis();
-        long userVisibleTimeDelta = (userDesiredDuration.unit.toMillis(userDesiredDuration.value));
+        long userVisibleTimeDelta = userDesiredDuration.asMilliseconds();
         Range<Long> desiredRange = new Range<>(now - userVisibleTimeDelta, now);
 
         if (availableRange.equals(newAvailableRange)) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/ToggleableReferenceFieldLabelDecorator.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import com.redhat.thermostat.annotations.ExtensionPoint;
+import com.redhat.thermostat.common.ActionListener;
+
+/**
+ * A {@link ReferenceFieldLabelDecorator} which can be enabled and disabled, so that the decorations
+ * it applies to reference fields do not need to always be applied.
+ */
+@ExtensionPoint
+public interface ToggleableReferenceFieldLabelDecorator extends ReferenceFieldLabelDecorator {
+
+    /**
+     * Represents this decorator's status changing from enabled to disabled or vice-versa.
+     */
+    enum StatusEvent {
+        STATUS_CHANGED,
+        ;
+    }
+
+    /**
+     * Set whether or not this decorator should apply its decoration to reference fields.
+     * Changes in the enabled state fire action events to status listeners.
+     */
+    void setEnabled(boolean enabled);
+
+    /**
+     * Check whether or not this decorator is currently enabled.
+     */
+    boolean isEnabled();
+
+    /**
+     * Add a listener which will be notified when this decorator's enabled/disabled status changes.
+     */
+    void addStatusEventListener(ActionListener<StatusEvent> listener);
+
+    /**
+     * Remove a status listener.
+     */
+    void removeStatusEventListener(ActionListener<StatusEvent> listener);
+
+}
--- a/client/living-vm-filter/core/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/living-vm-filter/core/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <artifactId>thermostat-osgi-living-vm-filter</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   <artifactId>thermostat-osgi-living-vm-filter-core</artifactId>
   <packaging>bundle</packaging>
--- a/client/living-vm-filter/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/living-vm-filter/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-client</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-osgi-living-vm-filter</artifactId>
--- a/client/living-vm-filter/swing/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/living-vm-filter/swing/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <artifactId>thermostat-osgi-living-vm-filter</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   <artifactId>thermostat-osgi-living-vm-filter-swing</artifactId>
   <packaging>bundle</packaging>
--- a/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/host/swing/HostInfoLabelDecorator.java	Mon Feb 08 18:37:19 2016 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright 2012-2015 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.host.swing;
-
-import java.util.List;
-
-import com.redhat.thermostat.client.ui.ReferenceFieldLabelDecorator;
-import com.redhat.thermostat.storage.core.HostRef;
-import com.redhat.thermostat.storage.core.Ref;
-import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
-import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
-
-public class HostInfoLabelDecorator implements ReferenceFieldLabelDecorator {
-
-    private NetworkInterfaceInfoDAO dao;
-    
-    public HostInfoLabelDecorator(NetworkInterfaceInfoDAO  dao) {
-        this.dao = dao;
-    }
-    
-    @Override
-    public int getOrderValue() {
-        return ORDER_FIRST;
-    }
-    
-    @Override
-    public String getLabel(String originalLabel, Ref reference) {
-        
-        if (!(reference instanceof HostRef)) {
-            return originalLabel;
-        }
-        
-        List<NetworkInterfaceInfo> infos =
-                dao.getNetworkInterfaces((HostRef) reference);
-        StringBuilder result = new StringBuilder();
-        
-        for (NetworkInterfaceInfo info : infos) {
-            // filter out the loopbak
-            if (!info.getInterfaceName().equals("lo")) {
-                if (info.getIp4Addr() != null) {
-                    result.append(info.getIp4Addr()).append("; ");
-                } else if (info.getIp6Addr() != null) {
-                    result.append(info.getIp6Addr()).append("; ");
-                }
-            }
-        }
-        // Avoid IOOBE if there are no network interfaces
-        if (result.length() >= 2) {
-            result.deleteCharAt(result.length() - 2);
-        }
-        return result.toString().trim();
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/host/swing/HostNetworkInterfaceLabelDecorator.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2012-2015 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.host.swing;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.Ref;
+import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
+
+public class HostNetworkInterfaceLabelDecorator implements ToggleableReferenceFieldLabelDecorator {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private NetworkInterfaceInfoDAO dao;
+    private boolean enabled = false;
+    private final Map<HostRef, String> hostNetworkIfaceMap = new HashMap<>();
+
+    private final ActionNotifier<StatusEvent> notifier = new ActionNotifier<>(this);
+
+    public HostNetworkInterfaceLabelDecorator(NetworkInterfaceInfoDAO dao) {
+        this.dao = dao;
+    }
+    
+    @Override
+    public int getOrderValue() {
+        return ORDER_FIRST;
+    }
+    
+    @Override
+    public String getLabel(String originalLabel, Ref reference) {
+
+        if (!isEnabled()) {
+            return originalLabel;
+        }
+        
+        if (!(reference instanceof HostRef)) {
+            return originalLabel;
+        }
+
+        HostRef ref = (HostRef) reference;
+        String label;
+        if (hostNetworkIfaceMap.containsKey(ref)) {
+            label = hostNetworkIfaceMap.get(ref);
+        } else {
+            label = computeLabel(ref);
+            hostNetworkIfaceMap.put(ref, label);
+        }
+
+        return t.localize(LocaleResources.NET_IFACE_LABEL_DECORATOR, originalLabel, label).getContents();
+    }
+
+    private String computeLabel(HostRef ref) {
+        List<NetworkInterfaceInfo> infos =
+                dao.getNetworkInterfaces(ref);
+        StringBuilder result = new StringBuilder();
+
+        for (NetworkInterfaceInfo info : infos) {
+            // filter out the loopbak
+            if (!info.getInterfaceName().equals("lo")) {
+                if (info.getIp4Addr() != null) {
+                    result.append(info.getIp4Addr()).append("; ");
+                } else if (info.getIp6Addr() != null) {
+                    result.append(info.getIp6Addr()).append("; ");
+                }
+            }
+        }
+        // Avoid IOOBE if there are no network interfaces
+        if (result.length() >= 2) {
+            result.deleteCharAt(result.length() - 2);
+        }
+        return result.toString().trim();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (this.enabled != enabled) {
+            this.enabled = enabled;
+            notifier.fireAction(StatusEvent.STATUS_CHANGED);
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void addStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.addActionListener(listener);
+    }
+
+    @Override
+    public void removeStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.removeActionListener(listener);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/host/swing/HostNetworkInterfaceLabelMenuAction.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2015 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.host.swing;
+
+import com.redhat.thermostat.client.ui.MenuAction;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class HostNetworkInterfaceLabelMenuAction implements MenuAction {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private HostNetworkInterfaceLabelDecorator decorator;
+
+    public HostNetworkInterfaceLabelMenuAction(HostNetworkInterfaceLabelDecorator decorator) {
+        this.decorator = decorator;
+    }
+
+    @Override
+    public LocalizedString getName() {
+        return t.localize(LocaleResources.NET_IFACE_LABEL_MENU_NAME);
+    }
+
+    @Override
+    public LocalizedString getDescription() {
+        return t.localize(LocaleResources.NET_IFACE_LABEL_MENU_DESCRIPTION);
+    }
+
+    @Override
+    public void execute() {
+        decorator.setEnabled(!decorator.isEnabled());
+    }
+
+    @Override
+    public Type getType() {
+        return Type.CHECK;
+    }
+
+    @Override
+    public LocalizedString[] getPath() {
+        return new LocalizedString[] { t.localize(LocaleResources.NET_IFACE_LABEL_MENU_PATH), getName() };
+    }
+
+    @Override
+    public int sortOrder() {
+        return SORT_TOP + 15;
+    }
+
+    @Override
+    public String getPersistenceID() {
+        return MENU_KEY + "host-net-iface-labels";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/host/swing/LocaleResources.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2015 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.host.swing;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    NET_IFACE_LABEL_MENU_NAME,
+    NET_IFACE_LABEL_MENU_DESCRIPTION,
+    NET_IFACE_LABEL_MENU_PATH,
+    NET_IFACE_LABEL_DECORATOR,
+    ;
+
+    static final String RESOURCE_BUNDLE =
+            "com.redhat.thermostat.client.filter.host.swing.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/LocaleResources.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    PID_LABEL_MENU_NAME,
+    PID_LABEL_MENU_DESCRIPTION,
+    PID_LABEL_MENU_PATH,
+    PID_LABEL_DECORATOR,
+
+    STARTTIME_LABEL_MENU_NAME,
+    STARTTIME_LABEL_MENU_DESCRIPTION,
+    STARTTIME_LABEL_MENU_PATH,
+    STARTTIME_LABEL_DECORATOR,
+    ;
+
+    static final String RESOURCE_BUNDLE =
+            "com.redhat.thermostat.client.filter.vm.swing.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+
+}
--- a/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMFilterActivator.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMFilterActivator.java	Tue Feb 09 12:39:50 2016 -0500
@@ -44,13 +44,15 @@
 import java.util.List;
 import java.util.Map;
 
+import com.redhat.thermostat.client.filter.host.swing.HostNetworkInterfaceLabelMenuAction;
+import com.redhat.thermostat.client.ui.MenuAction;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.client.filter.host.swing.DeadHostIconDecorator;
 import com.redhat.thermostat.client.filter.host.swing.HostIconDecorator;
-import com.redhat.thermostat.client.filter.host.swing.HostInfoLabelDecorator;
+import com.redhat.thermostat.client.filter.host.swing.HostNetworkInterfaceLabelDecorator;
 import com.redhat.thermostat.client.filter.host.swing.HostVmMainLabelDecorator;
 import com.redhat.thermostat.client.filter.host.swing.ThermostatVmMainLabelDecorator;
 import com.redhat.thermostat.client.swing.ReferenceFieldDecoratorLayout;
@@ -103,21 +105,43 @@
 
                 Dictionary<String, String> decoratorProperties = new Hashtable<>();
                 
-                VMLabelDecorator vmLabelDecorator = new VMLabelDecorator(vmDao);
+                VMPidLabelDecorator vmPidLabelDecorator = new VMPidLabelDecorator(vmDao);
                 decoratorProperties = new Hashtable<>();
                 decoratorProperties.put(ReferenceFieldLabelDecorator.ID,
                                         ReferenceFieldDecoratorLayout.LABEL_INFO.name());
 
                 registration = context.registerService(ReferenceFieldLabelDecorator.class.getName(),
-                        vmLabelDecorator, decoratorProperties);
-                
+                        vmPidLabelDecorator, decoratorProperties);
+
+                registeredServices.add(registration);
+
+                VMPidLabelMenuAction vmPidMenuAction = new VMPidLabelMenuAction(vmPidLabelDecorator);
+                registration = context.registerService(MenuAction.class.getName(),
+                        vmPidMenuAction, null);
+
                 registeredServices.add(registration);
-                
+
+                VMStartTimeLabelDecorator vmStartTimeLabelDecorator = new VMStartTimeLabelDecorator(vmDao);
+                decoratorProperties = new Hashtable<>();
+                decoratorProperties.put(ReferenceFieldLabelDecorator.ID,
+                        ReferenceFieldDecoratorLayout.LABEL_INFO.name());
+
+                registration = context.registerService(ReferenceFieldLabelDecorator.class.getName(),
+                        vmStartTimeLabelDecorator, decoratorProperties);
+
+                registeredServices.add(registration);
+
+                VMStartTimeLabelMenuAction vmStartTimeLabelMenuAction = new VMStartTimeLabelMenuAction(vmStartTimeLabelDecorator);
+                registration = context.registerService(MenuAction.class.getName(),
+                        vmStartTimeLabelMenuAction, null);
+
+                registeredServices.add(registration);
+
                 NetworkInterfaceInfoDAO networkDao = (NetworkInterfaceInfoDAO)
                             services.get(NetworkInterfaceInfoDAO.class.getName());
-                
-                HostInfoLabelDecorator hostLabelDecorator =
-                            new HostInfoLabelDecorator(networkDao);
+
+                HostNetworkInterfaceLabelDecorator hostLabelDecorator =
+                            new HostNetworkInterfaceLabelDecorator(networkDao);
                 decoratorProperties = new Hashtable<>();
                 decoratorProperties.put(ReferenceFieldLabelDecorator.ID,
                                         ReferenceFieldDecoratorLayout.LABEL_INFO.name());
@@ -125,6 +149,11 @@
                 registration = context.registerService(ReferenceFieldLabelDecorator.class.getName(),
                                                        hostLabelDecorator, decoratorProperties);
                 registeredServices.add(registration);
+
+                HostNetworkInterfaceLabelMenuAction hostNetworkMenuAction = new HostNetworkInterfaceLabelMenuAction(hostLabelDecorator);
+                registration = context.registerService(MenuAction.class.getName(), hostNetworkMenuAction, null);
+
+                registeredServices.add(registration);
                 
                 HostIconDecorator hostIconDecorator =
                             HostIconDecorator.createInstance(uiDefaults);
@@ -164,6 +193,7 @@
                 
                 registration = context.registerService(ReferenceFieldLabelDecorator.class.getName(),
                                                        mainDecorator, decoratorProperties);
+                registeredServices.add(registration);
                 
                 ThermostatVmMainLabelDecorator thermostatDecorator = new ThermostatVmMainLabelDecorator(vmDao);
                 decoratorProperties = new Hashtable<>();
--- a/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMLabelDecorator.java	Mon Feb 08 18:37:19 2016 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-/*
- * Copyright 2012-2015 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.swing;
-
-import com.redhat.thermostat.client.ui.ReferenceFieldLabelDecorator;
-import com.redhat.thermostat.storage.core.Ref;
-import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.storage.dao.VmInfoDAO;
-
-/**
- * Replaces the given ReferenceField label with the pid of the current
- * {@link VmRef}.
- */
-public class VMLabelDecorator implements ReferenceFieldLabelDecorator {
-
-    private VmInfoDAO dao;
-    
-    public VMLabelDecorator(VmInfoDAO dao) {
-        this.dao = dao;
-    }
-    
-    @Override
-    public int getOrderValue() {
-        return ORDER_CPU_GROUP;
-    }
-    
-    @Override
-    public String getLabel(String originalLabel, Ref reference) {
-        
-        if (!(reference instanceof VmRef)) {
-            return originalLabel;
-        }
-        
-        // replace the label with the information we really care about
-        int pid =  dao.getVmInfo((VmRef) reference).getVmPid();
-        return "Pid: " + pid;
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMPidLabelDecorator.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.Ref;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Appends the given ReferenceField label with the PID of the current
+ * {@link VmRef}.
+ */
+public class VMPidLabelDecorator implements ToggleableReferenceFieldLabelDecorator {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private VmInfoDAO dao;
+    private boolean enabled = false;
+    private final Map<VmRef, Integer> referencePidMap = new HashMap<>();
+
+    private final ActionNotifier<StatusEvent> notifier = new ActionNotifier<>(this);
+    
+    public VMPidLabelDecorator(VmInfoDAO dao) {
+        this.dao = dao;
+    }
+    
+    @Override
+    public int getOrderValue() {
+        return ORDER_CPU_GROUP;
+    }
+    
+    @Override
+    public String getLabel(String originalLabel, Ref reference) {
+
+        if (!isEnabled()) {
+            return originalLabel;
+        }
+
+        if (!(reference instanceof VmRef)) {
+            return originalLabel;
+        }
+
+        VmRef vmRef = (VmRef) reference;
+
+        if (!referencePidMap.containsKey(vmRef)) {
+            int pid =  dao.getVmInfo(vmRef).getVmPid();
+            referencePidMap.put(vmRef, pid);
+        }
+
+        String pid = Integer.toString(referencePidMap.get(vmRef));
+        return t.localize(LocaleResources.PID_LABEL_DECORATOR, originalLabel, pid).getContents();
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (this.enabled != enabled) {
+            this.enabled = enabled;
+            notifier.fireAction(StatusEvent.STATUS_CHANGED);
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void addStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.addActionListener(listener);
+    }
+
+    @Override
+    public void removeStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.removeActionListener(listener);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMPidLabelMenuAction.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.MenuAction;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class VMPidLabelMenuAction implements MenuAction {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private VMPidLabelDecorator decorator;
+
+    public VMPidLabelMenuAction(VMPidLabelDecorator decorator) {
+        this.decorator = decorator;
+    }
+
+    @Override
+    public LocalizedString getName() {
+        return t.localize(LocaleResources.PID_LABEL_MENU_NAME);
+    }
+
+    @Override
+    public LocalizedString getDescription() {
+        return t.localize(LocaleResources.PID_LABEL_MENU_DESCRIPTION);
+    }
+
+    @Override
+    public void execute() {
+        decorator.setEnabled(!decorator.isEnabled());
+    }
+
+    @Override
+    public Type getType() {
+        return MenuAction.Type.CHECK;
+    }
+
+    @Override
+    public LocalizedString[] getPath() {
+        return new LocalizedString[] { t.localize(LocaleResources.PID_LABEL_MENU_PATH), getName() };
+    }
+
+    @Override
+    public int sortOrder() {
+        return SORT_TOP + 10;
+    }
+
+    @Override
+    public String getPersistenceID() {
+        return MENU_KEY + "-pid-labels";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMStartTimeLabelDecorator.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.Ref;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class VMStartTimeLabelDecorator implements ToggleableReferenceFieldLabelDecorator {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private VmInfoDAO dao;
+    private boolean enabled = false;
+    private final Map<VmRef, Date> referenceTimestampMap = new HashMap<>();
+
+    private final ActionNotifier<StatusEvent> notifier = new ActionNotifier<>(this);
+
+    public VMStartTimeLabelDecorator(VmInfoDAO dao) {
+        this.dao = dao;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (this.enabled != enabled) {
+            this.enabled = enabled;
+            notifier.fireAction(StatusEvent.STATUS_CHANGED);
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void addStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.addActionListener(listener);
+    }
+
+    @Override
+    public void removeStatusEventListener(ActionListener<StatusEvent> listener) {
+        notifier.removeActionListener(listener);
+    }
+
+    @Override
+    public String getLabel(String originalLabel, Ref reference) {
+        if (!isEnabled()) {
+            return originalLabel;
+        }
+
+        if (!(reference instanceof VmRef)) {
+            return originalLabel;
+        }
+
+        VmRef ref = (VmRef) reference;
+
+        if (!referenceTimestampMap.containsKey(ref)) {
+            long timestamp = dao.getVmInfo(ref).getStartTimeStamp();
+            referenceTimestampMap.put(ref, new Date(timestamp));
+        }
+
+        String timestamp = Clock.DEFAULT_DATE_FORMAT.format(referenceTimestampMap.get(ref));
+        return t.localize(LocaleResources.STARTTIME_LABEL_DECORATOR, originalLabel, timestamp).getContents();
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER_CODE_GROUP;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/java/com/redhat/thermostat/client/filter/vm/swing/VMStartTimeLabelMenuAction.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.MenuAction;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class VMStartTimeLabelMenuAction implements MenuAction {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private VMStartTimeLabelDecorator decorator;
+
+    public VMStartTimeLabelMenuAction(VMStartTimeLabelDecorator decorator) {
+        this.decorator = decorator;
+    }
+
+    @Override
+    public LocalizedString getName() {
+        return t.localize(LocaleResources.STARTTIME_LABEL_MENU_NAME);
+    }
+
+    @Override
+    public LocalizedString getDescription() {
+        return t.localize(LocaleResources.STARTTIME_LABEL_MENU_DESCRIPTION);
+    }
+
+    @Override
+    public void execute() {
+        decorator.setEnabled(!decorator.isEnabled());
+    }
+
+    @Override
+    public Type getType() {
+        return Type.CHECK;
+    }
+
+    @Override
+    public LocalizedString[] getPath() {
+        return new LocalizedString[] { t.localize(LocaleResources.STARTTIME_LABEL_MENU_PATH), getName() };
+    }
+
+    @Override
+    public int sortOrder() {
+        return SORT_TOP + 15;
+    }
+
+    @Override
+    public String getPersistenceID() {
+        return MENU_KEY + "-start-time-labels";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/resources/com/redhat/thermostat/client/filter/host/swing/strings.properties	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,4 @@
+NET_IFACE_LABEL_MENU_NAME=Show Host IP addresses
+NET_IFACE_LABEL_MENU_DESCRIPTION=Show Host network interface addresses in the host tree
+NET_IFACE_LABEL_MENU_PATH=View
+NET_IFACE_LABEL_DECORATOR={0}\n{1}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/main/resources/com/redhat/thermostat/client/filter/vm/swing/strings.properties	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,8 @@
+PID_LABEL_MENU_NAME=Show VM PIDs
+PID_LABEL_MENU_DESCRIPTION=Show VM PIDs in the host tree
+PID_LABEL_MENU_PATH=View
+PID_LABEL_DECORATOR={0}\nPID: {1}
+STARTTIME_LABEL_MENU_NAME=Show VM start times
+STARTTIME_LABEL_MENU_DESCRIPTION=Show VM start times in the host tree
+STARTTIME_LABEL_MENU_PATH=View
+STARTTIME_LABEL_DECORATOR={0}\nStart time: {1}
\ No newline at end of file
--- a/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/host/swing/HostInfoLabelDecoratorTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright 2012-2015 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.host.swing;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.storage.core.HostRef;
-import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
-import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
-
-public class HostInfoLabelDecoratorTest {
-
-    @Test
-    public void testBasicWithNetworkInterfaces() {
-        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
-        String ip = "192.168.0.1";
-        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
-        assertEquals(ip, info.getInterfaceName());
-        info.setIp4Addr(ip);
-        networkList.add(info);
-        HostRef mockHostRef = mock(HostRef.class);
-        NetworkInterfaceInfoDAO dao = mock(NetworkInterfaceInfoDAO.class);
-        HostInfoLabelDecorator decorator = new HostInfoLabelDecorator(dao);
-        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
-        String decoratedLabel = decorator.getLabel("", mockHostRef);
-        assertEquals("192.168.0.1", decoratedLabel);
-    }
-    
-    @Test
-    public void testBasicTwoIfaces() {
-        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
-        String ip = "192.168.0.1";
-        String ip2 = "10.0.0.1";
-        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
-        NetworkInterfaceInfo info2 = new NetworkInterfaceInfo("foo-agent", ip2);
-        assertEquals(ip2, info2.getInterfaceName());
-        assertEquals(ip, info.getInterfaceName());
-        info.setIp4Addr(ip);
-        info2.setIp4Addr(ip2);
-        networkList.add(info);
-        networkList.add(info2);
-        HostRef mockHostRef = mock(HostRef.class);
-        NetworkInterfaceInfoDAO dao = mock(NetworkInterfaceInfoDAO.class);
-        HostInfoLabelDecorator decorator = new HostInfoLabelDecorator(dao);
-        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
-        String decoratedLabel = decorator.getLabel("", mockHostRef);
-        assertEquals("192.168.0.1; 10.0.0.1", decoratedLabel);
-    }
-    
-    @Test
-    public void testWithEmptyIp4AddrAndNoIPv6Addr() {
-        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
-        String ip = "192.168.0.1";
-        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
-        assertEquals(ip, info.getInterfaceName());
-        info.setIp4Addr(""); // empty string
-        networkList.add(info);
-        HostRef mockHostRef = mock(HostRef.class);
-        NetworkInterfaceInfoDAO dao = mock(NetworkInterfaceInfoDAO.class);
-        HostInfoLabelDecorator decorator = new HostInfoLabelDecorator(dao);
-        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
-        String decoratedLabel = decorator.getLabel("", mockHostRef);
-        assertEquals("", decoratedLabel);
-    }
-    
-    /**
-     * This should not throw IndexOutOfBoundsException
-     */
-    @Test
-    public void testWithNoNetworkInterfaces() {
-        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
-        HostRef mockHostRef = mock(HostRef.class);
-        NetworkInterfaceInfoDAO dao = mock(NetworkInterfaceInfoDAO.class);
-        HostInfoLabelDecorator decorator = new HostInfoLabelDecorator(dao);
-        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
-        String decoratedLabel = decorator.getLabel("", mockHostRef);
-        assertEquals("", decoratedLabel);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/host/swing/HostNetworkInterfaceLabelDecoratorTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2012-2015 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.host.swing;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.storage.core.VmRef;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
+import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+public class HostNetworkInterfaceLabelDecoratorTest {
+
+    private NetworkInterfaceInfoDAO dao;
+    private HostNetworkInterfaceLabelDecorator decorator;
+
+    @Before
+    public void setup() {
+        dao = mock(NetworkInterfaceInfoDAO.class);
+        decorator = new HostNetworkInterfaceLabelDecorator(dao);
+    }
+
+    @Test
+    public void testBasicWithNetworkInterfaces() {
+        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
+        String ip = "192.168.0.1";
+        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
+        assertEquals(ip, info.getInterfaceName());
+        info.setIp4Addr(ip);
+        networkList.add(info);
+        HostRef mockHostRef = mock(HostRef.class);
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
+        String decoratedLabel = decorator.getLabel("", mockHostRef).trim();
+        assertEquals("192.168.0.1", decoratedLabel);
+    }
+    
+    @Test
+    public void testBasicTwoIfaces() {
+        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
+        String ip = "192.168.0.1";
+        String ip2 = "10.0.0.1";
+        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
+        NetworkInterfaceInfo info2 = new NetworkInterfaceInfo("foo-agent", ip2);
+        assertEquals(ip2, info2.getInterfaceName());
+        assertEquals(ip, info.getInterfaceName());
+        info.setIp4Addr(ip);
+        info2.setIp4Addr(ip2);
+        networkList.add(info);
+        networkList.add(info2);
+        HostRef mockHostRef = mock(HostRef.class);
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
+        String decoratedLabel = decorator.getLabel("", mockHostRef).trim();
+        assertEquals("192.168.0.1; 10.0.0.1", decoratedLabel);
+    }
+    
+    @Test
+    public void testWithEmptyIp4AddrAndNoIPv6Addr() {
+        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
+        String ip = "192.168.0.1";
+        NetworkInterfaceInfo info = new NetworkInterfaceInfo("foo-agent", ip);
+        assertEquals(ip, info.getInterfaceName());
+        info.setIp4Addr(""); // empty string
+        networkList.add(info);
+        HostRef mockHostRef = mock(HostRef.class);
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
+        String decoratedLabel = decorator.getLabel("", mockHostRef).trim();
+        assertEquals("", decoratedLabel);
+    }
+    
+    /**
+     * This should not throw IndexOutOfBoundsException
+     */
+    @Test
+    public void testWithNoNetworkInterfaces() {
+        List<NetworkInterfaceInfo> networkList = new ArrayList<>();
+        HostRef mockHostRef = mock(HostRef.class);
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(any(HostRef.class))).thenReturn(networkList);
+        String decoratedLabel = decorator.getLabel("", mockHostRef).trim();
+        assertEquals("", decoratedLabel);
+    }
+
+    @Test
+    public void verifyGetLabelCaches() {
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(isA(HostRef.class))).thenReturn(new ArrayList<NetworkInterfaceInfo>());
+        HostRef ref = mock(HostRef.class);
+
+        decorator.getLabel("", ref);
+        verify(dao).getNetworkInterfaces(ref);
+
+        decorator.getLabel("", ref);
+        verify(dao).getNetworkInterfaces(ref); // still only once -> cached after first call
+    }
+
+    @Test
+    public void verifyGetLabelAppends() {
+        decorator.setEnabled(true);
+        when(dao.getNetworkInterfaces(isA(HostRef.class))).thenReturn(new ArrayList<NetworkInterfaceInfo>());
+        HostRef ref = mock(HostRef.class);
+
+        String str = decorator.getLabel("Foo", ref).trim();
+        assertThat(str, containsString("Foo"));
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenDisabled() {
+        decorator.setEnabled(false);
+        decorator.getLabel("", mock(HostRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenWrongRefType() {
+        decorator.setEnabled(true);
+        decorator.getLabel("", mock(VmRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void testSetEnabled() {
+        decorator.setEnabled(true);
+        assertThat(decorator.isEnabled(), is(true));
+        decorator.setEnabled(false);
+        assertThat(decorator.isEnabled(), is(false));
+    }
+
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testTogglingEnabledFiresEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(!decorator.isEnabled());
+
+        ArgumentCaptor<ActionEvent> captor = ArgumentCaptor.forClass(ActionEvent.class);
+        verify(listener).actionPerformed(captor.capture());
+
+        ActionEvent event = captor.getValue();
+        assertThat((ToggleableReferenceFieldLabelDecorator.StatusEvent) event.getActionId(),
+                is(ToggleableReferenceFieldLabelDecorator.StatusEvent.STATUS_CHANGED));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSettingSameEnabledValueDoesNotFireEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(decorator.isEnabled());
+
+        verify(listener, never()).actionPerformed(Matchers.<ActionEvent<ToggleableReferenceFieldLabelDecorator.StatusEvent>>any());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/AbstractToggleableMenuActionTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.MenuAction;
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.internal.matchers.GreaterOrEqual;
+import org.mockito.internal.matchers.LessOrEqual;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public abstract class AbstractToggleableMenuActionTest<T extends MenuAction> {
+
+    protected ToggleableReferenceFieldLabelDecorator decorator;
+    protected T action;
+
+    @Before
+    public abstract void setup();
+
+    @Test
+    public void verifyExecuteTogglesDecorator() {
+        when(decorator.isEnabled()).thenReturn(false);
+        action.execute();
+        verify(decorator).setEnabled(true);
+        when(decorator.isEnabled()).thenReturn(true);
+        action.execute();
+        verify(decorator).setEnabled(false);
+    }
+
+    @Test
+    public void assertSortOrderWithinBounds() {
+        int sortOrder = action.sortOrder();
+        assertThat(sortOrder, is(new GreaterOrEqual<>(MenuAction.SORT_TOP)));
+        assertThat(sortOrder, is(new LessOrEqual<>(MenuAction.SORT_BOTTOM)));
+    }
+
+    @Test
+    public void assertPersistenceIdContainsMenuKey() {
+        assertThat(action.getPersistenceID(), containsString(MenuAction.MENU_KEY));
+    }
+
+    @Test
+    public void assertPathContainsName() {
+        assertThat(action.getPath(), containsLocalizedString(action.getName()));
+    }
+
+    private static Matcher<LocalizedString[]> containsLocalizedString(final LocalizedString ls) {
+        return new BaseMatcher<LocalizedString[]>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof LocalizedString[])) {
+                    return false;
+                }
+                boolean contains = false;
+                for (LocalizedString str : ((LocalizedString[]) o)) {
+                    contains = contains || str.getContents().equals(ls.getContents());
+                }
+                return contains;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("LocalizedString[] containing element with contents ")
+                        .appendValue(ls.getContents());
+            }
+        };
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/HostNetworkInterfaceLabelMenuActionTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.filter.host.swing.HostNetworkInterfaceLabelDecorator;
+import com.redhat.thermostat.client.filter.host.swing.HostNetworkInterfaceLabelMenuAction;
+
+import static org.mockito.Mockito.mock;
+
+public class HostNetworkInterfaceLabelMenuActionTest extends AbstractToggleableMenuActionTest<HostNetworkInterfaceLabelMenuAction> {
+
+    @Override
+    public void setup() {
+        decorator = mock(HostNetworkInterfaceLabelDecorator.class);
+        action = new HostNetworkInterfaceLabelMenuAction((HostNetworkInterfaceLabelDecorator) decorator);
+    }
+
+}
--- a/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMFilterActivatorTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMFilterActivatorTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -40,12 +40,13 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.redhat.thermostat.client.ui.MenuAction;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
 import com.redhat.thermostat.annotations.internal.CacioTest;
 import com.redhat.thermostat.client.filter.host.swing.DeadHostIconDecorator;
-import com.redhat.thermostat.client.filter.host.swing.HostInfoLabelDecorator;
+import com.redhat.thermostat.client.filter.host.swing.HostNetworkInterfaceLabelDecorator;
 import com.redhat.thermostat.client.swing.UIDefaults;
 import com.redhat.thermostat.client.ui.ReferenceFieldIconDecorator;
 import com.redhat.thermostat.client.ui.ReferenceFieldLabelDecorator;
@@ -75,12 +76,16 @@
         ctx.registerService(NetworkInterfaceInfoDAO.class, netDao, null);
         ctx.registerService(UIDefaults.class, uiDefaults, null);
 
-        assertTrue(ctx.isServiceRegistered(ReferenceFieldLabelDecorator.class.getName(), VMLabelDecorator.class));
-        assertTrue(ctx.isServiceRegistered(ReferenceFieldLabelDecorator.class.getName(), HostInfoLabelDecorator.class));
+        assertTrue(ctx.isServiceRegistered(ReferenceFieldLabelDecorator.class.getName(), VMPidLabelDecorator.class));
+        assertTrue(ctx.isServiceRegistered(ReferenceFieldLabelDecorator.class.getName(), VMStartTimeLabelDecorator.class));
+        assertTrue(ctx.isServiceRegistered(ReferenceFieldLabelDecorator.class.getName(), HostNetworkInterfaceLabelDecorator.class));
         assertTrue(ctx.isServiceRegistered(ReferenceFieldIconDecorator.class.getName(), DeadHostIconDecorator.class));
 
         assertTrue(ctx.isServiceRegistered(ReferenceFieldIconDecorator.class.getName(), VMIconDecorator.class));
         assertTrue(ctx.isServiceRegistered(ReferenceFieldIconDecorator.class.getName(), DeadVMIconDecorator.class));
+
+        assertTrue(ctx.isServiceRegistered(MenuAction.class.getName(), VMPidLabelMenuAction.class));
+        assertTrue(ctx.isServiceRegistered(MenuAction.class.getName(), VMStartTimeLabelMenuAction.class));
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMPidLabelDecoratorTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.VmInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class VMPidLabelDecoratorTest {
+
+    private VmInfoDAO dao;
+    private VMPidLabelDecorator decorator;
+
+    @Before
+    public void setup() {
+        dao = mock(VmInfoDAO.class);
+        decorator = new VMPidLabelDecorator(dao);
+    }
+
+    @Test
+    public void testGetLabel() {
+        decorator.setEnabled(true);
+
+        VmInfo vmInfo = mock(VmInfo.class);
+        when(vmInfo.getVmPid()).thenReturn(100);
+        when(dao.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
+
+        VmRef ref = mock(VmRef.class);
+        String str = decorator.getLabel("Foo", ref);
+        assertThat(str, containsString("Foo"));
+        assertThat(str, containsString("PID"));
+        assertThat(str, containsString("100"));
+    }
+
+    @Test
+    public void verifyGetLabelCaches() {
+        decorator.setEnabled(true);
+        when(dao.getVmInfo(isA(VmRef.class))).thenReturn(mock(VmInfo.class));
+        VmRef ref = mock(VmRef.class);
+
+        decorator.getLabel("", ref);
+        verify(dao).getVmInfo(ref);
+
+        decorator.getLabel("", ref);
+        verify(dao).getVmInfo(ref); // still only once -> cached after first call
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenDisabled() {
+        decorator.setEnabled(false);
+        decorator.getLabel("", mock(VmRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenWrongRefType() {
+        decorator.setEnabled(true);
+        decorator.getLabel("", mock(HostRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void testSetEnabled() {
+        decorator.setEnabled(true);
+        assertThat(decorator.isEnabled(), is(true));
+        decorator.setEnabled(false);
+        assertThat(decorator.isEnabled(), is(false));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testTogglingEnabledFiresEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(!decorator.isEnabled());
+
+        ArgumentCaptor<ActionEvent> captor = ArgumentCaptor.forClass(ActionEvent.class);
+        verify(listener).actionPerformed(captor.capture());
+
+        ActionEvent event = captor.getValue();
+        assertThat((ToggleableReferenceFieldLabelDecorator.StatusEvent) event.getActionId(),
+                is(ToggleableReferenceFieldLabelDecorator.StatusEvent.STATUS_CHANGED));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSettingSameEnabledValueDoesNotFireEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(decorator.isEnabled());
+
+        verify(listener, never()).actionPerformed(Matchers.<ActionEvent<ToggleableReferenceFieldLabelDecorator.StatusEvent>>any());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMPidLabelMenuActionTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import org.junit.Before;
+
+import static org.mockito.Mockito.mock;
+
+public class VMPidLabelMenuActionTest extends AbstractToggleableMenuActionTest<VMPidLabelMenuAction> {
+
+    @Before
+    public void setup() {
+        decorator = mock(VMPidLabelDecorator.class);
+        action = new VMPidLabelMenuAction((VMPidLabelDecorator) decorator);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMStartTimeLabelDecoratorTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.VmInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class VMStartTimeLabelDecoratorTest {
+
+    private VmInfoDAO dao;
+    private VMStartTimeLabelDecorator decorator;
+
+    @Before
+    public void setup() {
+        dao = mock(VmInfoDAO.class);
+        decorator = new VMStartTimeLabelDecorator(dao);
+    }
+
+    @Test
+    public void testGetLabel() {
+        decorator.setEnabled(true);
+
+        VmInfo vmInfo = mock(VmInfo.class);
+        when(vmInfo.getStartTimeStamp()).thenReturn(100L);
+        when(dao.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
+
+        VmRef ref = mock(VmRef.class);
+        String str = decorator.getLabel("Foo", ref);
+        assertThat(str, containsString("Foo"));
+        assertThat(str, containsString("Start time"));
+        // formatted date/time string is not checked for because this can change depending on locale of test runner, etc.,
+        // and is a UI detail anyway
+    }
+
+    @Test
+    public void verifyGetLabelCaches() {
+        decorator.setEnabled(true);
+        when(dao.getVmInfo(isA(VmRef.class))).thenReturn(mock(VmInfo.class));
+        VmRef ref = mock(VmRef.class);
+
+        decorator.getLabel("", ref);
+        verify(dao).getVmInfo(ref);
+
+        decorator.getLabel("", ref);
+        verify(dao).getVmInfo(ref); // still only once -> cached after first call
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenDisabled() {
+        decorator.setEnabled(false);
+        decorator.getLabel("", mock(VmRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void verifyNoDaoAccessWhenWrongRefType() {
+        decorator.setEnabled(true);
+        decorator.getLabel("", mock(HostRef.class));
+        verifyZeroInteractions(dao);
+    }
+
+    @Test
+    public void testSetEnabled() {
+        decorator.setEnabled(true);
+        assertThat(decorator.isEnabled(), is(true));
+        decorator.setEnabled(false);
+        assertThat(decorator.isEnabled(), is(false));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testTogglingEnabledFiresEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(!decorator.isEnabled());
+
+        ArgumentCaptor<ActionEvent> captor = ArgumentCaptor.forClass(ActionEvent.class);
+        verify(listener).actionPerformed(captor.capture());
+
+        ActionEvent event = captor.getValue();
+        assertThat((ToggleableReferenceFieldLabelDecorator.StatusEvent) event.getActionId(),
+                is(ToggleableReferenceFieldLabelDecorator.StatusEvent.STATUS_CHANGED));
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testSettingSameEnabledValueDoesNotFireEvent() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        decorator.addStatusEventListener(listener);
+
+        decorator.setEnabled(decorator.isEnabled());
+
+        verify(listener, never()).actionPerformed(Matchers.<ActionEvent<ToggleableReferenceFieldLabelDecorator.StatusEvent>>any());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/living-vm-filter/swing/src/test/java/com/redhat/thermostat/client/filter/vm/swing/VMStartTimeLabelMenuActionTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012-2015 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.swing;
+
+import com.redhat.thermostat.client.ui.MenuAction;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.internal.matchers.GreaterOrEqual;
+import org.mockito.internal.matchers.LessOrEqual;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class VMStartTimeLabelMenuActionTest {
+
+    private VMStartTimeLabelDecorator decorator;
+    private VMStartTimeLabelMenuAction action;
+
+    @Before
+    public void setup() {
+        decorator = mock(VMStartTimeLabelDecorator.class);
+        action = new VMStartTimeLabelMenuAction(decorator);
+    }
+
+    @Test
+    public void verifyExecuteTogglesDecorator() {
+        when(decorator.isEnabled()).thenReturn(false);
+        action.execute();
+        verify(decorator).setEnabled(true);
+        when(decorator.isEnabled()).thenReturn(true);
+        action.execute();
+        verify(decorator).setEnabled(false);
+    }
+
+    @Test
+    public void assertSortOrderWithinBounds() {
+        int sortOrder = action.sortOrder();
+        assertThat(sortOrder, is(new GreaterOrEqual<>(MenuAction.SORT_TOP)));
+        assertThat(sortOrder, is(new LessOrEqual<>(MenuAction.SORT_BOTTOM)));
+    }
+
+    @Test
+    public void assertPersistenceIdContainsMenuKey() {
+        assertThat(action.getPersistenceID(), containsString(MenuAction.MENU_KEY));
+    }
+
+    @Test
+    public void assertPathContainsName() {
+        assertThat(action.getPath(), containsLocalizedString(action.getName()));
+    }
+
+    private static Matcher<LocalizedString[]> containsLocalizedString(final LocalizedString ls) {
+        return new BaseMatcher<LocalizedString[]>() {
+            @Override
+            public boolean matches(Object o) {
+                if (!(o instanceof LocalizedString[])) {
+                    return false;
+                }
+                boolean contains = false;
+                for (LocalizedString str : ((LocalizedString[]) o)) {
+                    contains = contains || str.getContents().equals(ls.getContents());
+                }
+                return contains;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("LocalizedString[] containing element with contents ")
+                        .appendValue(ls.getContents());
+            }
+        };
+    }
+
+}
--- a/client/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-client</artifactId>
--- a/client/swing-components/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing-components/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <artifactId>thermostat-client</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-swing-components</artifactId>
--- a/client/swing/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-client</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-client-swing</artifactId>
@@ -150,6 +150,15 @@
     </resources>
 
     <plugins>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <forkMode>always</forkMode>
+        </configuration>
+      </plugin>
+
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButton.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButton.java	Tue Feb 09 12:39:50 2016 -0500
@@ -43,8 +43,12 @@
 import com.redhat.thermostat.client.core.ToggleActionState;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 
+import java.util.Objects;
+
 @SuppressWarnings("serial")
 public class ActionToggleButton extends JToggleButton implements ToolbarButton {
+
+    private static final ToggleActionState DEFAULT_TOGGLE_ACTION_STATE = new DefaultToggleActionState();
         
     private String lastText;
     private boolean showText;
@@ -67,11 +71,12 @@
         showText = true;
         setText(text.getContents());
 
-        buttonUI = new ActionToggleButtonUI();
+        buttonUI = new ActionToggleButtonUI(DEFAULT_TOGGLE_ACTION_STATE);
         setUI(buttonUI);
         setOpaque(false);
         setContentAreaFilled(false);
         setBorder(new ToolbarButtonBorder(this));
+        setToggleActionState(DEFAULT_TOGGLE_ACTION_STATE);
     }
     
     @Override
@@ -102,11 +107,29 @@
     }
 
     public void setToggleActionState(ToggleActionState toggleActionState) {
+        Objects.requireNonNull(toggleActionState);
         setEnabled(!toggleActionState.isTransitionState() && toggleActionState.isButtonEnabled());
         setSelected(toggleActionState.isActionEnabled());
         buttonUI.setState(toggleActionState);
         repaint();
     }
 
+    private static class DefaultToggleActionState implements ToggleActionState {
+        @Override
+        public boolean isTransitionState() {
+            return false;
+        }
+
+        @Override
+        public boolean isActionEnabled() {
+            return false;
+        }
+
+        @Override
+        public boolean isButtonEnabled() {
+            return true;
+        }
+    }
+
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButtonUI.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ActionToggleButtonUI.java	Tue Feb 09 12:39:50 2016 -0500
@@ -46,6 +46,7 @@
 import java.awt.Image;
 import java.awt.Rectangle;
 import java.awt.image.BufferedImage;
+import java.util.Objects;
 
 /**
  * An ActionButtonUI which is intended specifically for "togglebuttons", which
@@ -69,6 +70,10 @@
     protected Image disabledSelectedIcon;
     private ToggleActionState toggleActionState;
 
+    ActionToggleButtonUI(ToggleActionState initialState) {
+        this.toggleActionState = Objects.requireNonNull(initialState);
+    }
+
     @Override
     protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
         AbstractButton button = (AbstractButton) c;
@@ -115,16 +120,9 @@
             x = button.getWidth() / 2 - w / 2;
         }
 
-        boolean transitionState, actionEnabled, buttonEnabled;
-        if (toggleActionState == null) {
-            transitionState = false;
-            actionEnabled = false;
-            buttonEnabled = false;
-        } else {
-            transitionState = toggleActionState.isTransitionState();
-            actionEnabled = toggleActionState.isActionEnabled();
-            buttonEnabled = toggleActionState.isButtonEnabled();
-        }
+        boolean transitionState = toggleActionState.isTransitionState();
+        boolean actionEnabled = toggleActionState.isActionEnabled();
+        boolean buttonEnabled = toggleActionState.isButtonEnabled();
 
         boolean stopped = !transitionState && !actionEnabled;
         boolean starting = transitionState && actionEnabled;
@@ -153,7 +151,7 @@
     }
 
     void setState(ToggleActionState toggleActionState) {
-        this.toggleActionState = toggleActionState;
+        this.toggleActionState = Objects.requireNonNull(toggleActionState);
     }
 
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/FontAwesomeIcon.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/FontAwesomeIcon.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,8 +36,12 @@
 
 package com.redhat.thermostat.client.swing.components;
 
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
 import java.awt.Color;
 import java.awt.Component;
+import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontFormatException;
 import java.awt.FontMetrics;
@@ -46,6 +50,9 @@
 import java.awt.Image;
 import java.awt.Paint;
 import java.awt.RenderingHints;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.InputStream;
@@ -71,9 +78,8 @@
     private Paint color;
     
     static {
-        try {
-            InputStream stream =
-                    FontAwesomeIcon.class.getClassLoader().getResourceAsStream(AWESOME_SET);
+        ClassLoader loader = FontAwesomeIcon.class.getClassLoader();
+        try (InputStream stream = loader.getResourceAsStream(AWESOME_SET)) {
             awesome = Font.createFont(Font.TRUETYPE_FONT, stream);
 
         } catch (FontFormatException | IOException ex) {
@@ -131,13 +137,20 @@
 
             graphics.setFont(font);
             graphics.setPaint(color);
-            
-            FontMetrics metrics = graphics.getFontMetrics(font);
-            int width = metrics.charWidth(iconID);
+
+            String icon = String.valueOf(iconID);
+
+            FontRenderContext fontRenderContext = graphics.getFontRenderContext();
+            TextLayout layout = new TextLayout(icon, font, fontRenderContext);
 
-            int stringX = (int) (getIconWidth()/2 - width/2 + .5);
-            int stringY = getIconHeight() - (getIconHeight()/4) + 1;
-            graphics.drawString(String.valueOf(iconID), stringX, stringY);            
+            FontMetrics metrics = graphics.getFontMetrics(font);
+
+            int stringX = (getIconWidth() - metrics.stringWidth(icon))/2;
+            int stringY = (metrics.getAscent() + (getIconHeight() - (metrics.getAscent() + metrics.getDescent())) / 2);
+
+            graphics.drawString(String.valueOf(iconID), stringX, stringY);
+
+            graphics.setColor(Color.RED);
             graphics.dispose();
         }
         
@@ -158,5 +171,6 @@
     public int getIconWidth() {
         return size;
     }
+
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HeaderPanel.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HeaderPanel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -58,7 +58,9 @@
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
 
+import com.redhat.thermostat.client.swing.internal.LocaleResources;
 import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
 
 /**
  * A component that host a panel with a nicely rendered header.
@@ -68,6 +70,8 @@
         
     public static final String SHOW_TEXT = "SHOW_TEXT";
     
+    private static final Translate<LocaleResources> translate = LocaleResources.createLocalizer();
+
     private boolean showText;
     
     private LocalizedString header;
@@ -187,10 +191,9 @@
     class PreferencesPopup extends ThermostatPopupMenu {
         JMenuItem preferencesMenu;
         public PreferencesPopup() {
-            // TODO: localize
-            String text = "Show button text";
+            String text = translate.localize(LocaleResources.SHOW_BUTTON_TEXT).getContents();
             if (showText) {
-                text = "Hide button text";
+                text = translate.localize(LocaleResources.HIDE_BUTTON_TEXT).getContents();
             }
             preferencesMenu = new JMenuItem(text);
             preferencesMenu.addActionListener(new ActionListener() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HtmlShadowLabel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components;
+
+import com.redhat.thermostat.common.utils.StringUtils;
+
+public class HtmlShadowLabel extends ShadowLabel {
+
+    @Override
+    public void setText(String text) {
+        super.setText(htmlize(text));
+    }
+
+    private static String htmlize(String text) {
+        text = StringUtils.htmlEscape(text.trim()).replaceAll("\r", "").replaceAll("\n", "<br>");
+        return "<html>" + text + "</html>";
+    }
+
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/MultiChartPanel.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/MultiChartPanel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -63,9 +63,9 @@
 import org.jfree.data.time.TimeSeriesCollection;
 import org.jfree.data.xy.XYDataset;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.ui.ChartColors;
 import com.redhat.thermostat.client.ui.RecentTimeSeriesChartController;
+import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/RecentTimeSeriesChartPanel.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/RecentTimeSeriesChartPanel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -44,11 +44,11 @@
 import javax.swing.SwingUtilities;
 import javax.swing.text.JTextComponent;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.swing.components.experimental.RecentTimeControlPanel;
 import org.jfree.chart.ChartPanel;
 
 import com.redhat.thermostat.client.ui.RecentTimeSeriesChartController;
+import com.redhat.thermostat.common.Duration;
 
 public class RecentTimeSeriesChartPanel extends JPanel {
 
@@ -85,7 +85,7 @@
             @Override
             public void propertyChange(final PropertyChangeEvent evt) {
                 Duration d = (Duration) evt.getNewValue();
-                controller.setTime(d.value, d.unit);
+                controller.setTime(d.getValue(), d.getUnit());
             }
         });
         add(recentTimeControlPanel, BorderLayout.SOUTH);
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ThermostatTableRenderer.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ThermostatTableRenderer.java	Tue Feb 09 12:39:50 2016 -0500
@@ -66,6 +66,18 @@
         return result;
     }
 
+    @Override
+    protected void setValue(Object value) {
+        // lets this renderer display icons correctly
+        if (value instanceof Icon) {
+            setAlignmentX(CENTER_ALIGNMENT);
+            setIcon((Icon) value);
+        } else {
+            setAlignmentX(LEFT_ALIGNMENT);
+            super.setValue(value);
+        }
+    }
+
     private boolean isEven(int row) {
         return row % 2 == 0;
     }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/RecentTimeControlPanel.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/RecentTimeControlPanel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -49,9 +49,9 @@
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.text.JTextComponent;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.locale.LocaleResources;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.shared.locale.Translate;
 
 public class RecentTimeControlPanel extends JPanel {
@@ -92,13 +92,13 @@
                 Duration d = (Duration) actionEvent.getPayload();
                 RecentTimeControlPanel.this.firePropertyChange(PROPERTY_VISIBLE_TIME_RANGE, null, d);
             }
-        }, duration.value, duration.unit);
+        }, duration.getValue(), duration.getUnit());
 
         durationSelector.getDocument().addDocumentListener(timeUnitChangeListener);
         unitSelector.addActionListener(timeUnitChangeListener);
 
-        durationSelector.setText(String.valueOf(duration.value));
-        unitSelector.setSelectedItem(duration.unit);
+        durationSelector.setText(String.valueOf(duration.getValue()));
+        unitSelector.setSelectedItem(duration.getUnit());
 
         durationSelector.setName("durationSelector");
         unitSelector.setName("unitSelector");
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanel.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanel.java	Tue Feb 09 12:39:50 2016 -0500
@@ -51,6 +51,8 @@
 import javax.swing.text.JTextComponent;
 
 import com.redhat.thermostat.client.ui.SampledDataset;
+import com.redhat.thermostat.common.Duration;
+
 import org.jfree.chart.ChartMouseEvent;
 import org.jfree.chart.ChartMouseListener;
 import org.jfree.chart.ChartPanel;
@@ -60,7 +62,6 @@
 import org.jfree.chart.plot.Crosshair;
 import org.jfree.chart.plot.XYPlot;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.swing.components.ValueField;
 import org.jfree.ui.RectangleEdge;
 
@@ -125,7 +126,7 @@
         chart.getXYPlot().getRangeAxis().setAutoRange(true);
 
         chart.getXYPlot().getDomainAxis().setAutoRange(true);
-        chart.getXYPlot().getDomainAxis().setFixedAutoRange(initialDuration.unit.toMillis(initialDuration.value));
+        chart.getXYPlot().getDomainAxis().setFixedAutoRange(initialDuration.asMilliseconds());
 
         chart.getPlot().setBackgroundPaint(WHITE);
         chart.getPlot().setBackgroundImageAlpha(TRANSPARENT);
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SquarifiedTreeMap.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/SquarifiedTreeMap.java	Tue Feb 09 12:39:50 2016 -0500
@@ -38,6 +38,7 @@
 
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
@@ -52,12 +53,31 @@
  *  @see TreMapBuilder
  */
 public class SquarifiedTreeMap {
+
+    /*
+     * The algorithm this implements is described in detail here:
+     *
+     * https://bitbucket.org/Ammirate/thermostat-treemap/src/tip/TreeMap%20documentation.pdf
+     *
+     * Which is an improvement on:
+     *
+     * Mark Bruls, Kees Huizing and Jarke J. van Wijk, "Squarified
+     * Treemaps" in Data Visualization 2000: Proceedings of the Joint
+     * EUROGRAPHICS and IEEE TCVG Symposium on Visualization in Amsterdam,
+     * The Netherlands, May 29–30, 2000. Berlin, Germany: Springer Science
+     * & Business Media, 2012
+     *
+     * The paper itself is also available online at:
+     * https://www.win.tue.nl/~vanwijk/stm.pdf
+     */
     
     /**
      * List of node to represent as TreeMap.
      */
     private LinkedList<TreeMapNode> elements;
-    
+
+    private double totalRealWeight;
+
     /**
      * Represent the area in which draw nodes.
      */
@@ -76,7 +96,9 @@
     /**
      * The rectangles area available for drawing.
      */
-    private Rectangle2D.Double availableArea;
+    private Rectangle2D.Double availableRegion;
+
+    private double initialArea;
 
     /**
      * List of the calculated rectangles.
@@ -94,77 +116,91 @@
     private double lastX = 0;
     private double lastY = 0;
 
-
-    /**
-     * Constructor.
-     * 
-     * @param d the dimension of the total area in which draw elements.
-     * @param list the list of elements to draw as TreeMap.
-     * 
-     * @throws a NullPointerException if one of the arguments is null.
-     */
     public SquarifiedTreeMap(Rectangle2D.Double bounds, List<TreeMapNode> list) {
         this.elements = new LinkedList<>();
-        elements.addAll(Objects.requireNonNull(list));
+        this.elements.addAll(Objects.requireNonNull(list));
+        this.totalRealWeight = getRealSum(elements);
         this.container = Objects.requireNonNull(bounds);
+        this.squarifiedNodes = new ArrayList<>();
+        this.currentRow = new ArrayList<>();
     }
 
     /**
-     * Invoke this method to calculate the rectangles for the TreeMap.
-     * 
-     * @return a list of node having a rectangle built in percentage to the 
-     * available area.
+     * This method prepares for and initiates the process of determining rectangles to represent
+     * nodes.
+     *
+     * @return a list of nodes, each containing their respective rectangle.
      */
     public List<TreeMapNode> squarify() {
-        initializeArea();
-        prepareData(elements);
+        if (elements.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        initialArea = container.getWidth() * container.getHeight();
+        availableRegion = new Rectangle2D.Double(container.getX(), container.getY(),
+                container.getWidth(), container.getHeight());
+        lastX = 0;
+        lastY = 0;
+        updateDirection();
+
+        TreeMapNode.sort(elements);
+
         List<TreeMapNode> row = new ArrayList<>();
-        double w = getPrincipalSide();	
-        squarify(elements, row, w);
+        squarifyHelper(elements, row, 0, getPrincipalSide());
         return getSquarifiedNodes();
     }
 
     /**
-     * Calculate recursively the rectangles to draw and their size.
-     * 
-     * @param nodes the list of elements to draw.
-     * @param row the list of current rectangles to process.
-     * @param w the side against which to calculate the rectangles.
+     * Recursively determine the rectangles that represent the set of nodes.
+     *
+     * @param nodes remaining nodes to be processed.
+     * @param row the nodes that have been included in the row currently under construction.
+     * @param rowArea the total area allocated to this row.
+     * @param side the length of the side against which to calculate the the aspect ratio.
      */
-    private void squarify(LinkedList<TreeMapNode> nodes, List<TreeMapNode> row, double w) {
+    private void squarifyHelper(LinkedList<TreeMapNode> nodes, List<TreeMapNode> row,
+                                double rowArea, double side) {
+
         if (nodes.isEmpty() && row.isEmpty()) {
-            // work done
+            // nothing to do here, just return
             return;
         }
         if (nodes.isEmpty()) {
-            // no more element to process, just draw current row
-            finalizeRow(row);
+            // no more nodes to process, just finalize current row
+            finalizeRow(row, rowArea);
             return;
         }
         if (row.isEmpty()) {
-            // add the first element to the row and iterate the process over it
+            // add the first element to the row and process any remaining nodes recursively
             row.add(nodes.getFirst());
+            double realWeight = nodes.getFirst().getRealWeight();
             nodes.removeFirst();
-            squarify(nodes, row, w);
+            double nodeArea = (realWeight / totalRealWeight) * initialArea;
+            squarifyHelper(nodes, row, nodeArea, side);
             return;
         }
-        
-        /*  Greedy step: calculate the best aspect ratio of actual row and the
-         *  best aspect ratio given by adding another rectangle to the row.
-         *  If the current row can not be improved then finalize it
-         *  else add the next element, to improve the global aspect ratio
+
+        /*
+         * Determine if adding another rectangle to the current row improves the overall aspect
+         * ratio.  If the current row is not (and therefore cannot be) improved then it is
+         * finalized, and the algorithm is run recursively on the remaining nodes that have not yet
+         * been placed in a row.
          */
-        List<TreeMapNode> expandedRow = new ArrayList<TreeMapNode>(row);
+        List<TreeMapNode> expandedRow = new ArrayList<>(row);
         expandedRow.add(nodes.getFirst());
-        double actualAspectRatio = bestAspectRatio(row, w);
-        double expandedAspectRatio = bestAspectRatio(expandedRow, w);
+        double realWeight = nodes.getFirst().getRealWeight();
+        double nodeArea = (realWeight / totalRealWeight) * initialArea;
+        double expandedRowArea = rowArea + nodeArea;
+
+        double actualAspectRatio = maxAspectRatio(row, rowArea, side);
+        double expandedAspectRatio = maxAspectRatio(expandedRow, expandedRowArea, side);
 
         if (!willImprove(actualAspectRatio, expandedAspectRatio)) {
-            finalizeRow(row);
-            squarify(nodes, new ArrayList<TreeMapNode>(), getPrincipalSide());
+            finalizeRow(row, rowArea);
+            squarifyHelper(nodes, new ArrayList<TreeMapNode>(), 0, getPrincipalSide());
         } else {
             nodes.removeFirst();
-            squarify(nodes, expandedRow, w);
+            squarifyHelper(nodes, expandedRow, expandedRowArea, side);
         }
     }
 
@@ -175,25 +211,12 @@
     public List<TreeMapNode> getSquarifiedNodes() {
         return squarifiedNodes;
     }
-    
-    /**
-     * Initialize the available area used to create the tree map
-     */
-    private void initializeArea() {
-        availableArea = new Rectangle2D.Double(container.getX(), container.getY(), 
-                container.getWidth(), container.getHeight());
-        lastX = 0;
-        lastY = 0;
-        squarifiedNodes = new ArrayList<>();
-        currentRow = new ArrayList<>();
-        updateDirection();
-    }
-    
+
     /**
      * Recalculate the drawing direction.
      */
     private void updateDirection() {
-        drawingDir = availableArea.getWidth() > availableArea.getHeight() ? 
+        drawingDir = availableRegion.getWidth() > availableRegion.getHeight() ?
                 DIRECTION.TOP_BOTTOM : DIRECTION.LEFT_RIGHT;
     }
 
@@ -207,31 +230,27 @@
     }
     
     /**
-     * Keep the current list of nodes which produced the best aspect ratio
-     * in the available area, draw their respective rectangles and reinitialize 
-     * the current row to draw.
-     * <p>
-     * @param nodes the list of numbers which represent the rectangles' area.
-     * @return the number of Rectangles created.
+     * For each node in the row, this method creates a rectangle to represent it graphically.
+     *
+     * @param row the set of nodes that constitute a row.
+     * @param rowArea the area allocated to the row.
      */
-    private void finalizeRow(List<TreeMapNode> nodes) {
-        if (nodes == null || nodes.isEmpty()) {
+    private void finalizeRow(List<TreeMapNode> row, double rowArea) {
+        if (row == null || row.isEmpty()) {
             return;
         }
-        // get the total weight of nodes in order to calculate their percentages
-        double sum = getSum(nodes);
-        // greedy optimization step: get the best aspect ratio for nodes drawn 
+
+        // greedy optimization step: get the best aspect ratio for nodes drawn
         // on the longer and on the smaller side, to evaluate the best.
-        double actualAR = bestAspectRatio(nodes, getPrincipalSide());
-        double alternativeAR = bestAspectRatio(nodes, getSecondarySide());
+        double actualAR = maxAspectRatio(row, rowArea, getPrincipalSide());
+        double alternativeAR = maxAspectRatio(row, rowArea, getSecondarySide());
       
         if (willImprove(actualAR, alternativeAR)) {
             invertDirection();
         }
 
-        for (TreeMapNode node: nodes) {
-            // assign a rectangle calculated as percentage of the total weight
-            Rectangle2D.Double r = createRectangle(sum, node.getWeight());
+        for (TreeMapNode node: row) {
+            Rectangle2D.Double r = createRectangle(rowArea, node.getRealWeight() / getRealSum(row));
             node.setRectangle(r);
             
             // recalculate coordinates to draw next rectangle
@@ -240,53 +259,40 @@
             // add the node to the current list of rectangles in processing
             currentRow.add(node);
         }
-        // recalculate the area in which new rectangles will be drawn and 
+        // recalculate the area in which new rectangles will be drawn and
         // reinitialize the current list of node to represent.
         reduceAvailableArea();
         newRow();
     }
     
-
     /**
-     * Create a rectangle having area = @param area in percentage of @param sum. 
-     * <p>
-     * For example: assume @param area = 4 and @param sum = 12 and the 
-     * drawing direction is top to bottom. <br>
-     * <p>
-     *   __ __ __ __
-     *  |     |     | 
-     *  |__ __|     |  
-     *  |__ __ __ __|
-     * 
-     * <br>the internal rectangle will be calculated as follow:<br>
-     *  {@code height = (4 / 9) * 3} <--note that the principal side for actual  
-     *  drawing direction is 3.
-     *  <br>Now it is possible to calculate the width:<br>
-     *  {@code width = 4 / 1.3} <-- note this is the height value
-     *  
-     * <p>
-     * @param sum the total size of all rectangles in the actual row.
-     * @param area this Rectangle's area.
-     * @return the Rectangle which correctly fill the available area.
+     * Create a rectangle that has a size determined by what fraction of the total row area is
+     * allocated to it.
+     *
+     * @param rowArea the total area allocated to the row.
+     * @param fraction the portion of the total area allocated to the rectangle being created.
+     * @return the created rectangle.
      */
-    private Rectangle2D.Double createRectangle(Double sum, Double area) {
+    private Rectangle2D.Double createRectangle(Double rowArea, Double fraction) {
         double side = getPrincipalSide();
         double w = 0;
         double h = 0;
         
-        //don't want division by 0
-        if (validate(area) == 0 || validate(sum) == 0 || validate(side) == 0) {
+        if (validate(fraction) == 0 || validate(rowArea) == 0 || validate(side) == 0) {
             return new Rectangle2D.Double(lastX, lastY, 0, 0);
         }
-        
-        // calculate the rectangle's principal side relatively to the container 
-        // rectangle's principal side.
+
         if (drawingDir == DIRECTION.TOP_BOTTOM) {
-            h = (area / sum) * side;
-            w = area / h;
+            // the length of the secondary side (width here) of the rectangle is consistent between
+            // rectangles in the row
+            w = rowArea / side;
+
+            // as the width is consistent, the length of the principal side (height here) of the
+            // rectangle is proportional to the ratio rectangleArea / rowArea = fraction.
+            h = fraction * side;
         } else {
-            w = (area / sum) * side;
-            h = area / w;
+            w = fraction * side;
+            h = rowArea / side;
         }        
         return new Rectangle2D.Double(lastX, lastY, w, h);
     }
@@ -310,7 +316,7 @@
      */
     private double getPrincipalSide() {
         return drawingDir == DIRECTION.LEFT_RIGHT ? 
-                availableArea.getWidth() : availableArea.getHeight();
+                availableRegion.getWidth() : availableRegion.getHeight();
     }
 
     /**
@@ -318,19 +324,14 @@
      * @return the secondary available area's side.
      */
     private double getSecondarySide() {
-        return drawingDir == DIRECTION.LEFT_RIGHT ? 
-                availableArea.getHeight() : availableArea.getWidth();
+        return drawingDir == DIRECTION.LEFT_RIGHT ?
+                availableRegion.getHeight() : availableRegion.getWidth();
     }
 
-    /**
-     * Sum the elements in the list.
-     * @param nodes the list which contains elements to sum.
-     * @return the sum of the elements.
-     */
-    private double getSum(List<TreeMapNode> nodes) {
+    private double getRealSum(List<TreeMapNode> nodes) {
         double sum = 0;
-        for (TreeMapNode n : nodes) {
-            sum += n.getWeight();
+        for (TreeMapNode node : nodes) {
+            sum += node.getRealWeight();
         }
         return sum;
     }
@@ -365,17 +366,17 @@
     private void reduceAvailableArea() {
         if (drawingDir == DIRECTION.LEFT_RIGHT) {
             // all rectangles inside the row have the same height
-            availableArea.height -= currentRow.get(0).getRectangle().height;
-            availableArea.y = lastY + currentRow.get(0).getRectangle().height;
-            availableArea.x = currentRow.get(0).getRectangle().x;
+            availableRegion.height -= currentRow.get(0).getRectangle().height;
+            availableRegion.y = lastY + currentRow.get(0).getRectangle().height;
+            availableRegion.x = currentRow.get(0).getRectangle().x;
         } else {
             // all rectangles inside the row have the same width
-            availableArea.width -= currentRow.get(0).getRectangle().width;
-            availableArea.x = lastX + currentRow.get(0).getRectangle().width;
-            availableArea.y = currentRow.get(0).getRectangle().y;
+            availableRegion.width -= currentRow.get(0).getRectangle().width;
+            availableRegion.x = lastX + currentRow.get(0).getRectangle().width;
+            availableRegion.y = currentRow.get(0).getRectangle().y;
         }
         updateDirection();
-        initializeXY(availableArea);
+        initializeXY(availableRegion);
     }
     
     /**
@@ -387,55 +388,34 @@
     }
 
     /**
-     * Calculate the aspect ratio for all the rectangles in the list and
-     * return the max of them.
-     * @param row the list of rectangles.
-     * @param side the side against which to calculate the the aspect ratio.
-     * @return the max aspect ratio calculated for the row.
+     * For each node in the row, determine the ratio longer side / shorter side of the rectangle
+     * that would represent it.  Return the maximum ratio.
+     *
+     * @param row the list of nodes in this row.
+     * @param rowArea the area allocated to this row.
+     * @param side the length of the side against which to calculate the the aspect ratio.
+     * @return the maximum ratio calculated for this row.
      */
-    private double bestAspectRatio(List<TreeMapNode> row, double side) {
+    private double maxAspectRatio(List<TreeMapNode> row, double rowArea, double side) {
         if (row == null || row.isEmpty()) {
             return Double.MAX_VALUE;
         }
-        double sum = getSum(row);
-        double max = 0;
-        // calculate the aspect ratio against the main side, and also its inverse.
-        // this is because aspect ratio of rectangle 6x4 can be calculated as 
-        // 6/4 but also 4/6. Here the aspect ratio has been calculated as 
-        // indicated in the Squarified algorithm.
+
+        double realSum = getRealSum(row);
+        double maxRatio = 0;
+
         for (TreeMapNode node : row) {
-            double m1 = (Math.pow(side, 2) * node.getWeight()) / Math.pow(sum, 2);
-            double m2 = Math.pow(sum, 2) / (Math.pow(side, 2) * node.getWeight());
-            double m = Math.max(m1, m2);
+            double fraction = node.getRealWeight() / realSum;
+            double length = rowArea / side;
+            double width = fraction * side;
+            double currentRatio = Math.max(length / width, width / length);
 
-            if (m > max) {
-                max = m;
+            if (currentRatio > maxRatio) {
+                maxRatio = currentRatio;
             }
         }
-        return max;
-    }
 
-    
-    /**
-     * Prepare the elements in the list, sorting them and transforming them
-     * proportionally the given dimension.
-     * @param dim the dimension in which rectangles will be drawn.
-     * @param elements the list of elements to draw.
-     * @return the list sorted and proportioned to the dimension.
-     */
-    private void  prepareData(List<TreeMapNode> elements) {
-        if (elements == null || elements.isEmpty()) {
-            return;
-        }
-        TreeMapNode.sort(elements);
-        double totArea = availableArea.width * availableArea.height;
-        double sum = getSum(elements);
-        
-        // recalculate weights in percentage of their sum
-        for (TreeMapNode node : elements) {
-            double w = (node.getWeight()/sum) * totArea;
-            node.setWeight(w);
-        }
+        return maxRatio;
     }
 
     /**
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimeUnitChangeListener.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimeUnitChangeListener.java	Tue Feb 09 12:39:50 2016 -0500
@@ -43,9 +43,9 @@
 import javax.swing.text.Document;
 import java.util.concurrent.TimeUnit;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Duration;
 
 public class TimeUnitChangeListener implements DocumentListener, java.awt.event.ActionListener {
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapComponent.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapComponent.java	Tue Feb 09 12:39:50 2016 -0500
@@ -76,8 +76,6 @@
 import javax.swing.border.LineBorder;
 
 import com.redhat.thermostat.client.swing.ThermostatSwingCursors;
-import com.redhat.thermostat.common.Size;
-import com.redhat.thermostat.common.Size.Unit;
 
 /**
  * This class allows to represent a hierarchical data structure as a TreeMap.
@@ -145,7 +143,7 @@
     /**
      * Variable in which store last resize event call time.
      */
-    private static long lastCall = 0;
+    private long lastCall = 0;
 
     /**
      * Wait time in millisec to resize the TreeMap.
@@ -162,7 +160,7 @@
      * This object stores the last clicked rectangle in the TreeMap, in order to 
      * repaint it when another rectangle will be selected.
      */
-    private static Comp lastClicked;
+    private Comp lastClicked;
     
     /**
      * List of objects observing this.
@@ -206,19 +204,9 @@
         this.tree = Objects.requireNonNull(tree);
         this.zoomStack.clear();
         this.zoomStack.push(this.tree);
-        resetTreeMapNodeWeights(this.tree);
         processAndDrawTreeMap(this.tree);
     }
 
-    private void resetTreeMapNodeWeights(TreeMapNode node) {
-        Objects.requireNonNull(node);
-        node.setWeight(node.getRealWeight());
-
-        for(TreeMapNode child : node.getChildren()) {
-            resetTreeMapNodeWeights(child);
-        }
-    }
-
     public void setToolTipRenderer(ToolTipRenderer renderer) {
         this.tooltipRenderer = renderer;
     }
@@ -431,13 +419,18 @@
      */
     void processAndDrawTreeMap(TreeMapNode root) {
         tree = Objects.requireNonNull(root);
-        Rectangle2D.Double newArea = tree.getRectangle();
+
+        if (getSize().width == 0 || getSize().height == 0) {
+            return;
+        }
+
+        Rectangle2D.Double region = tree.getRectangle();
         // give to the root node the size of this object so it can be recalculated
-        newArea.width = getSize().width;
-        newArea.height = getSize().height;
+        region.width = getSize().width;
+        region.height = getSize().height;
 
         // recalculate the tree
-        TreeProcessor.processTreeMap(tree, newArea);
+        TreeProcessor.processTreeMap(tree, region);
 
         removeAll();
         drawTreeMap(tree);
@@ -591,7 +584,7 @@
     /**
      * Switch the component's border style to the one given in input.
      *
-     * @param borderStyle the border style to use
+     * @param newBorderStyle the border style to use
      */
     public void setBorderStyle(int newBorderStyle) {
         Border border;
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapNode.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapNode.java	Tue Feb 09 12:39:50 2016 -0500
@@ -97,28 +97,16 @@
     private List<TreeMapNode> children;
     
     /**
-     * The node's weight.
-     */
-    private double weight;
-    
-    /**
      * The node's label. It can be the same of another node.
      */
     private String label;
     
     /**
-     * The node's weight which has been set inside the constructor. All
-     * operations which refers to node's weight work on the weight field, that 
-     * is used to make calcs.
+     * The node's weight.
      */
     private double realWeight;
     
     /**
-     * This flag indicates if weight value can be a non positive number.
-     */
-    static boolean allowNonPositiveWeight = false;
-    
-    /**
      * The color of this node.
      */
     private Color color;
@@ -173,7 +161,6 @@
         this.children = new ArrayList<TreeMapNode>();
         this.rectangle = new Rectangle2D.Double();
         this.info = new HashMap<String, String>();
-        this.weight = realWeight;
         this.realWeight = realWeight;
     }
 
@@ -292,22 +279,6 @@
     }
 
     /**
-     * Return the weight of this object. In case of allowNonPositiveWeight is 
-     * set to false and the weight is 0, less than 0 or not a number 
-     * ({@link Double.Nan}), this method returns a value that can be transformed
-     * by external objects, so if you need the real weight you have to
-     * invoke getrealWeight().
-     * 
-     * @return the node's weight.
-     */
-    public double getWeight() {
-        if ((weight <= 0 || weight == Double.NaN) && !allowNonPositiveWeight) {
-            return realWeight;
-        }
-        return this.weight;
-    }
-    
-    /**
      * Use this method to retrieve the real weight assigned to this node.
      * @return the weight corresponding to this node.
      */
@@ -324,16 +295,6 @@
     }
 
     /**
-     * Set the weight of this object. If a negative value is given, it is set 
-     * automatically to 0.
-     * @param weight the new weight for this object.
-     */
-    public void setWeight(double w) {
-        this.weight = w < 0 && !allowNonPositiveWeight ? 0 : w;
-    }
-
-
-    /**
      * Return the rectangle representing this object.
      * @return a {@link Rectangle2D.Double} object.
      */
@@ -353,23 +314,6 @@
     }    
 
     /**
-     * 
-     * @return true if non positive value can be used as weight, else false.
-     */
-    public static boolean isAllowNonPositiveWeight() {
-        return allowNonPositiveWeight;
-    }
-
-    /**
-     * Set this value to false and nodes will be not able to manage non positive
-     * values for weight field, otherwise set to true.
-     * @param allowed the flag value for managing non positive values as weight
-     */
-    public static void setAllowNonPositiveWeight(boolean allowed) {
-        allowNonPositiveWeight = allowed;
-    }
-
-    /**
      * This method assess if the rectangle associated to this node is drawable,
      * which means that its sides are greater than 1.
      * @return true if the rectangle associated to this node  is drawable, 
@@ -425,7 +369,7 @@
           @Override
           public int compare(TreeMapNode o1, TreeMapNode o2) {
               // inverting the result to descending sort the list
-              return -(Double.compare(o1.getWeight(), o2.getWeight()));
+              return -(Double.compare(o1.getRealWeight(), o2.getRealWeight()));
           }
       };
       Collections.sort(nodes, c);
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeProcessor.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TreeProcessor.java	Tue Feb 09 12:39:50 2016 -0500
@@ -74,9 +74,9 @@
      * node who has children.
      * @param node the subtree's root to process
      */
-    private static void process(TreeMapNode node) {                                                                                                                            
-        
-        SquarifiedTreeMap algorithm = new SquarifiedTreeMap(getSubArea(node.getRectangle()), node.getChildren());
+    private static void process(TreeMapNode node) {
+        SquarifiedTreeMap algorithm =
+                new SquarifiedTreeMap(getSubArea(node.getRectangle()), node.getChildren());
         node.setChildren(algorithm.squarify());
 
         Color c = node.getNextColor();
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/LocaleResources.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/LocaleResources.java	Tue Feb 09 12:39:50 2016 -0500
@@ -54,6 +54,10 @@
     CUT,
     COPY,
     PASTE,
+
+    SHOW_BUTTON_TEXT,
+    HIDE_BUTTON_TEXT,
+
     ;
 
     static final String RESOURCE_BUNDLE =
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImpl.java	Tue Feb 09 12:39:50 2016 -0500
@@ -166,18 +166,6 @@
         }
     };
 
-    private VMInformationRegistry vmInfoRegistry;
-    private ActionListener<ThermostatExtensionRegistry.Action> vmInfoRegistryListener =
-            new ActionListener<ThermostatExtensionRegistry.Action> ()
-    {
-        public void actionPerformed(com.redhat.thermostat.common.ActionEvent<ThermostatExtensionRegistry.Action>
-                                    actionEvent)
-        {
-            // TODO
-            // System.err.println(actionEvent.getPayload());
-        };
-    };
-
     private VmInformationControllerProvider vmInfoControllerProvider;
     private ReferenceFilterRegistry filterRegistry;
     private FilterManager filterManager;
@@ -204,7 +192,6 @@
             filterRegistry = registryFactory.createFilterRegistry();
             
             menuRegistry = registryFactory.createMenuRegistry();
-            vmInfoRegistry = registryFactory.createVMInformationRegistry();
             
         } catch (InvalidSyntaxException e) {
             throw new RuntimeException(e);
@@ -309,16 +296,16 @@
         // initially fill out with all known host and vms
         List<HostRef> hosts = networkMonitor.getHosts(new AllPassFilter<HostRef>());
         AllPassFilter<VmRef> vmFilter = new AllPassFilter<>();
+        filter.addHosts(hosts);
         for (HostRef host : hosts) {
             hostController.registerHost(host);
-            filter.addHost(host);
             
             // get the vm for this host
             List<VmRef> vms = hostMonitor.getVirtualMachines(host, vmFilter);
             for (VmRef vm : vms) {
                 hostController.registerVM(vm);
-                filter.addVM(vm);
             }
+            filter.addVMs(host, vms);
         }
     }
     
@@ -398,9 +385,6 @@
         filterManager = new FilterManager(filterRegistry, hostTreeController);
 
         filterManager.start();
-        
-        vmInfoRegistry.addActionListener(vmInfoRegistryListener);
-        vmInfoRegistry.start();
 
         setUpActionControllers();
 
@@ -428,10 +412,6 @@
 
         filterManager.stop();
         decoratorController.stop();
-        
-        vmInfoRegistry.removeActionListener(vmInfoRegistryListener);
-        vmInfoRegistryListener = null;
-        vmInfoRegistry.stop();
     }
 
     @Override
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/RegistryFactory.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/RegistryFactory.java	Tue Feb 09 12:39:50 2016 -0500
@@ -57,10 +57,6 @@
     MenuRegistry createMenuRegistry() throws InvalidSyntaxException {
         return new MenuRegistry(context);
     }
-    
-    VMInformationRegistry createVMInformationRegistry() throws InvalidSyntaxException {
-        return new VMInformationRegistry(context);
-    }
 
     DecoratorRegistryController createDecoratorController() {
         DecoratorRegistryFactory decoratorRegistryFactory =
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/VMInformationRegistry.java	Mon Feb 08 18:37:19 2016 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012-2015 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.internal;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
-
-import com.redhat.thermostat.client.core.InformationService;
-import com.redhat.thermostat.common.ThermostatExtensionRegistry;
-import com.redhat.thermostat.storage.core.VmRef;
-
-@SuppressWarnings("rawtypes")
-class VMInformationRegistry extends ThermostatExtensionRegistry<InformationService> {
-
-    private static final String FILTER = "(&(" + Constants.OBJECTCLASS + "=" +
-            InformationService.class.getName() + ")(" +
-            com.redhat.thermostat.common.Constants.GENERIC_SERVICE_CLASSNAME + "=" +
-            VmRef.class.getName() + "))";
-    
-    public VMInformationRegistry(BundleContext context) throws InvalidSyntaxException {
-        super(context, FILTER, InformationService.class);
-    }
-}
-
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/registry/decorator/DecoratorRegistryController.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/registry/decorator/DecoratorRegistryController.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,11 +36,21 @@
 
 package com.redhat.thermostat.client.swing.internal.registry.decorator;
 
+import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorListener;
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ThermostatExtensionRegistry;
 import org.osgi.framework.InvalidSyntaxException;
 
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorManager;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+
 public class DecoratorRegistryController {
 
     private DecoratorRegistryFactory registryFactory;
@@ -50,6 +60,8 @@
     private MainLabelDecoratorRegistry mainLabel;
     private IconDecoratorRegistry iconDecorator;
 
+    private ActionListener<ThermostatExtensionRegistry.Action> toggleableFilterListener;
+
     public DecoratorRegistryController(DecoratorRegistryFactory registryFactory) {
         this.registryFactory = registryFactory;
     }
@@ -68,25 +80,85 @@
     }
     
     public void start() {
+        ToggleStatusListener toggleStatusListener =
+                new ToggleStatusListener(Arrays.asList(decoratorManager.getInfoLabelDecoratorListener(),
+                decoratorManager.getMainLabelDecoratorListener(), decoratorManager.getIconDecoratorListener()));
+
+        toggleableFilterListener = new ToggleableDecoratorFilterListener(toggleStatusListener);
+
         infoLabel.addActionListener(decoratorManager.getInfoLabelDecoratorListener());
+        infoLabel.addActionListener(toggleableFilterListener);
         infoLabel.start();
         
         mainLabel.addActionListener(decoratorManager.getMainLabelDecoratorListener());
+        mainLabel.addActionListener(toggleableFilterListener);
         mainLabel.start();
         
         iconDecorator.addActionListener(decoratorManager.getIconDecoratorListener());
+        iconDecorator.addActionListener(toggleableFilterListener);
         iconDecorator.start();
     }
 
     public void stop() {
         infoLabel.removeActionListener(decoratorManager.getInfoLabelDecoratorListener());
+        infoLabel.removeActionListener(toggleableFilterListener);
         infoLabel.stop();
         
         mainLabel.removeActionListener(decoratorManager.getMainLabelDecoratorListener());
+        mainLabel.removeActionListener(toggleableFilterListener);
         mainLabel.stop();
         
         iconDecorator.removeActionListener(decoratorManager.getIconDecoratorListener());
+        iconDecorator.removeActionListener(toggleableFilterListener);
         iconDecorator.stop();
     }
+
+    ActionListener<ThermostatExtensionRegistry.Action> getToggleableFilterListener() {
+        return toggleableFilterListener;
+    }
+
+    static class ToggleableDecoratorFilterListener implements ActionListener<ThermostatExtensionRegistry.Action> {
+
+        private final ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener;
+
+        ToggleableDecoratorFilterListener(ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent<ThermostatExtensionRegistry.Action> actionEvent) {
+            if (!(actionEvent.getPayload() instanceof ToggleableReferenceFieldLabelDecorator)) {
+                return;
+            }
+            ToggleableReferenceFieldLabelDecorator toggleable = (ToggleableReferenceFieldLabelDecorator) actionEvent.getPayload();
+            switch (actionEvent.getActionId()) {
+                case SERVICE_ADDED:
+                    toggleable.addStatusEventListener(listener);
+                    break;
+                case SERVICE_REMOVED:
+                    toggleable.removeStatusEventListener(listener);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    static class ToggleStatusListener implements ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> {
+
+        private final HashSet<DecoratorListener<? extends Decorator>> listeners = new HashSet<>();
+
+        ToggleStatusListener(Collection<DecoratorListener<? extends Decorator>> listeners) {
+            this.listeners.addAll(listeners);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent<ToggleableReferenceFieldLabelDecorator.StatusEvent> actionEvent) {
+            for (DecoratorListener<? extends Decorator> listener : listeners) {
+                listener.fireDecorationChanged();
+            }
+        }
+    }
+
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/search/ReferenceFieldSearchFilter.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/search/ReferenceFieldSearchFilter.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.swing.internal.search;
 
+import java.util.Collection;
 import java.util.concurrent.atomic.AtomicReference;
 
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
@@ -54,7 +55,8 @@
  * place.
  */
 public class ReferenceFieldSearchFilter extends ReferenceFilter implements ActionListener<SearchAction> {
-        private SearchProvider searchProvider;
+
+    private SearchProvider searchProvider;
     private AtomicReference<String> searchString;
     
     private HostTreeController hostTreeController;
@@ -123,19 +125,34 @@
             notify(FilterEvent.FILTER_CHANGED);
         }
     }
-    
+
     public void addHost(HostRef host) {
         backend.addHost(host);
-        notify(FilterEvent.FILTER_CHANGED);
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefAddedPayload());
     }
 
-    public void removeHost(HostRef host) {}
+    public void addHosts(Collection<HostRef> hosts) {
+        backend.addHosts(hosts);
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefAddedPayload());
+    }
+
+    public void removeHost(HostRef host) {
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefRemovedPayload<>(host));
+    }
 
     public void addVM(VmRef vm) {
         backend.addVM(vm);
-        notify(FilterEvent.FILTER_CHANGED);
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefAddedPayload());
     }
 
-    public void removeVM(VmRef vm) {}
+    public void addVMs(HostRef host, Collection<VmRef> vms) {
+        backend.addVMs(host, vms);
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefAddedPayload());
+    }
+
+    public void removeVM(VmRef vm) {
+        notify(FilterEvent.FILTER_CHANGED, new HostTreeController.FilterRefRemovedPayload<>(vm));
+    }
+
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/search/SearchBackend.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/search/SearchBackend.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.client.swing.internal.search;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
@@ -89,8 +90,18 @@
         hosts.get(ref).add(vm);
     }
 
+    public synchronized void addVMs(HostRef host, Collection<VmRef> vms) {
+        hosts.get(host).addAll(vms);
+    }
+
     public synchronized void addHost(HostRef host) {
         hosts.put(host, new HashSet<VmRef>());
     }
+
+    public synchronized void addHosts(Collection<HostRef> hosts) {
+        for (HostRef host : hosts) {
+            addHost(host);
+        }
+    }
 }
 
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/ReferenceComponent.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/ReferenceComponent.java	Tue Feb 09 12:39:50 2016 -0500
@@ -46,6 +46,7 @@
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 
+import com.redhat.thermostat.client.swing.components.HtmlShadowLabel;
 import com.redhat.thermostat.client.swing.components.Icon;
 import com.redhat.thermostat.client.swing.components.ShadowLabel;
 
@@ -57,7 +58,7 @@
 public class ReferenceComponent extends JPanel implements AccordionComponent, ReferenceProvider {
 
     private ShadowLabel mainLabel;
-    private ShadowLabel infoLabel;
+    private HtmlShadowLabel infoLabel;
 
     private JLabel iconLabel;
 
@@ -94,7 +95,7 @@
         mainLabel = new ShadowLabel();
         mainLabel.setText(vm.getName());
         
-        infoLabel = new ShadowLabel();
+        infoLabel = new HtmlShadowLabel();
         infoLabel.setFont(infoLabel.getFont().deriveFont(8.0f));
         infoLabel.setText(vm.getStringID());
         
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/DecoratorManager.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/DecoratorManager.java	Tue Feb 09 12:39:50 2016 -0500
@@ -39,7 +39,6 @@
 import java.util.HashMap;
 
 import com.redhat.thermostat.client.swing.components.Icon;
-import com.redhat.thermostat.client.swing.internal.accordion.TitledPane;
 import com.redhat.thermostat.client.swing.internal.vmlist.ReferenceComponent;
 import com.redhat.thermostat.client.swing.internal.vmlist.ReferenceTitle;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorNotifier.DecorationEvent;
@@ -49,6 +48,8 @@
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.storage.core.Ref;
 
+import javax.swing.SwingUtilities;
+
 public class DecoratorManager {
 
     private class Icons {
@@ -150,7 +151,7 @@
     public DecoratorListener<ReferenceFieldIconDecorator> getIconDecoratorListener() {
         return iconDecorator;
     }
-    
+
     private class ListenerPayload {
         ActionListener<DecoratorNotifier.DecorationEvent> main;
         ActionListener<DecoratorNotifier.DecorationEvent> info;
@@ -167,8 +168,13 @@
         @Override
         public void actionPerformed(ActionEvent<DecorationEvent> actionEvent) {
             Ref ref = component.getReference();
-            String label = createComponentLabel(infoLabelDecorator, ref, "");
-            component.setInfoLabelText(label);
+            final String label = createComponentLabel(infoLabelDecorator, ref, "");
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    component.setInfoLabelText(label);
+                }
+            });
         }
     }
     
@@ -182,8 +188,13 @@
         @Override
         public void actionPerformed(ActionEvent<DecorationEvent> actionEvent) {
             Ref ref = component.getReference();
-            String label = createComponentLabel(mainLabelDecorator, ref, "");
-            component.setMainLabelText(label);
+            final String label = createComponentLabel(mainLabelDecorator, ref, "");
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    component.setMainLabelText(label);
+                }
+            });
         }
     }
     
@@ -196,7 +207,12 @@
         
         @Override
         public void actionPerformed(ActionEvent<DecorationEvent> actionEvent) {
-            setIcons(component);
+            SwingUtilities.invokeLater(new Runnable() {
+                @Override
+                public void run() {
+                    setIcons(component);
+                }
+            });
         }
     }
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/HostTreeController.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/vmlist/controller/HostTreeController.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,7 +36,9 @@
 
 package com.redhat.thermostat.client.swing.internal.vmlist.controller;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.swing.SwingUtilities;
@@ -63,25 +65,32 @@
 
 public class HostTreeController {
 
-    public static enum ReferenceSelection {
-        ITEM_SELECTED;
+    public enum ReferenceSelection {
+        ITEM_SELECTED,
     }
 
     private DecoratorManager decoratorManager;
     private HostTreeComponentFactory componentFactory;
-    
+
     private final ActionNotifier<ReferenceSelection> referenceNotifier;
 
+    /* The list of filters is updated infrequently and irregularly, but the list is iterated over and each filter
+     * checked and applied for all Refs in the AccordionModel on every rebuildTree. This is a significant slowdown,
+     * particularly at first start up if a large number of hosts and VMs are present. This map is used to cache the
+     * filter results, giving a significant performance improvement.
+     */
+    private final Map<Ref, Boolean> filterMap = new HashMap<>();
     private CopyOnWriteArrayList<ReferenceFilter> filters;
-    
+
     private FilterListener filterListener;
-    
+
     private Accordion<HostRef, VmRef> accordion;
-    
+
     // we keep a private fullModel updated with all the references, while we
     // apply filters and decorations to the accordion original model only 
     private AccordionModel<HostRef, VmRef> proxyModel;
     private AccordionModel<HostRef, VmRef> fullModel;
+
     private class AccordionModelProxy implements AccordionModelChangeListener<HostRef, VmRef> {
         @Override
         public synchronized void headerAdded(AccordionHeaderEvent<HostRef> e) {
@@ -103,21 +112,21 @@
             proxyModel.removeComponent(e.getHeader(), e.getComponent());
         }
     }
-    
+
     public HostTreeController(Accordion<HostRef, VmRef> accordion,
                               DecoratorManager decoratorManager,
                               HostTreeComponentFactory componentFactory)
     {
         this.accordion = accordion;
         this.componentFactory = componentFactory;
-        
+
         filterListener = new FilterListener();
-        
+
         filters = new CopyOnWriteArrayList<>();
-        
+
         fullModel = new AccordionModel<>();
         fullModel.addAccordionModelChangeListener(new AccordionModelProxy());
-        
+
         this.decoratorManager = decoratorManager;
         referenceNotifier = new ActionNotifier<>(this);
         this.proxyModel = accordion.getModel();
@@ -129,39 +138,39 @@
             }
         });
     }
-    
+
     public void addReferenceSelectionChangeListener(ActionListener<ReferenceSelection> listener) {
         referenceNotifier.addActionListener(listener);
     }
-    
+
     public void removeReferenceSelectionChangeListener(ActionListener<ReferenceSelection> listener) {
         referenceNotifier.removeActionListener(listener);
     }
-    
+
     public synchronized void registerHost(final HostRef host) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
                 fullModel.addHeader(host);
-                if (filter(filters, host)) {
+                if (filter(host)) {
                     proxyModel.removeHeader(host);
                 }
             }
         });
     }
-    
+
     public synchronized void updateHostStatus(final HostRef host) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                if (filter(filters, host)) {
+                if (filter(host)) {
                     proxyModel.removeHeader(host);
                 }
                 fireDecoratorChanged();
             }
         });
     }
-    
+
     private void addHostToProxy(final HostRef host) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
@@ -170,7 +179,7 @@
             }
         });
     }
-    
+
     private void setAccordionExpanded(final HostRef ref, final boolean expanded) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
@@ -179,7 +188,7 @@
             }
         });
     }
-    
+
     private void addVmToProxy(final VmRef vm) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
@@ -188,39 +197,45 @@
             }
         });
     }
-    
+
     private void addVMImpl(final VmRef vm) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
                 fullModel.addComponent(vm.getHostRef(), vm);
-                
+
                 // adding a vm may add an host, so we need to be sure
                 // the host is not filtered before checking the vm itself
-                if (filter(filters, vm.getHostRef())) {
-                    
+                if (filter(vm.getHostRef())) {
+
                     // this will also remove all the vm, so we can skip the
                     // next filtering step
                     proxyModel.removeHeader(vm.getHostRef());
-                    
-                } else if (filter(filters, vm)) {
+
+                } else if (filter(vm)) {
                     proxyModel.removeComponent(vm.getHostRef(), vm);
                 }
             }
         });
     }
 
-    private boolean filter(CopyOnWriteArrayList<ReferenceFilter> filters, Ref ref) {
+    private boolean filter(Ref ref) {
+        if (filterMap.containsKey(ref)) {
+            return filterMap.get(ref);
+        }
+        boolean filtered = false;
         for (ReferenceFilter filter : filters) {
             if (filter.applies(ref)) {
                 if (!filter.matches(ref)) {
-                    return true;
+                    filtered = true;
+                    break;
                 }
             }
         }
-        return false;
+        filterMap.put(ref, filtered);
+        return filtered;
     }
-    
+
     public synchronized void registerVM(final VmRef vm) {
         addVMImpl(vm);
     }
@@ -233,7 +248,7 @@
         }
         return component;
     }
-    
+
     private void selectComponent(final Ref _selected) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
@@ -251,11 +266,11 @@
                         selected = reference.getHostRef();
                     }
                 }
-                
+
                 if (selected instanceof HostRef) {
                     component = getHostComponent((HostRef) selected);
                 }
-                
+
                 // if this is not the case, let's just not select anything
                 if (component != null) {
                     accordion.setSelectedComponent(component);
@@ -263,25 +278,25 @@
             }
         });
     }
-    
+
     public synchronized void updateVMStatus(final VmRef vm) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                if (filter(filters, vm)) {
+                if (filter(vm)) {
                     proxyModel.removeComponent(vm.getHostRef(), vm);
                 }
                 fireDecoratorChanged();
             }
         });
     }
-            
+
     private void fireDecoratorChanged() {
         decoratorManager.getInfoLabelDecoratorListener().fireDecorationChanged();
         decoratorManager.getMainLabelDecoratorListener().fireDecorationChanged();
         decoratorManager.getIconDecoratorListener().fireDecorationChanged();
     }
-    
+
     private synchronized void rebuildTree() {
         Ref selected = null;
         AccordionComponent component = accordion.getSelectedComponent();
@@ -290,72 +305,80 @@
             // in the first place, but anyway... 
             selected = ((ReferenceProvider) component).getReference();
         }
-        
+
         // operate on a copy first since we need to know which of the ones we
         // have now was previously collapsed/expanded, we can't do this
         // if we empty the model first
         AccordionModel<RefPayload<HostRef>, RefPayload<VmRef>> _model = new AccordionModel<>();
         List<HostRef> hosts = fullModel.getHeaders();
         for (HostRef host : hosts) {
-            if (!filter(filters, host)) {
-                
-                RefPayload<HostRef> hostPayload = new RefPayload<>(); 
+            if (!filter(host)) {
+
+                RefPayload<HostRef> hostPayload = new RefPayload<>();
                 hostPayload.reference = host;
                 hostPayload.expanded = accordion.isExpanded(host);
 
                 _model.addHeader(hostPayload);
                 List<VmRef> vms = fullModel.getComponents(host);
                 for (VmRef vm : vms) {
-                    if (!filter(filters, vm)) {
-                        
-                        RefPayload<VmRef> vmPayload = new RefPayload<>(); 
-                        vmPayload.reference = vm;                        
+                    if (!filter(vm)) {
+
+                        RefPayload<VmRef> vmPayload = new RefPayload<>();
+                        vmPayload.reference = vm;
                         _model.addComponent(hostPayload, vmPayload);
                     }
                 }
             }
         }
-        
+
         // clear and refill, then expand as appropriate
         proxyModel.clear();
         List<RefPayload<HostRef>> payloadsHosts = _model.getHeaders();
         for (RefPayload<HostRef> host : payloadsHosts) {
             addHostToProxy(host.reference);
-            
+
             List<RefPayload<VmRef>> vms = _model.getComponents(host);
             for (RefPayload<VmRef> vm : vms) {
                 addVmToProxy(vm.reference);
             }
-            
+
             // need to do this after we add content to the host
             // or it won't take effect
             setAccordionExpanded(host.reference, host.expanded);
         }
-        
+
         // now select the entry that was originally selected, or its parent...
         // ... or nothing
         if (selected != null) {
             selectComponent(selected);
         }
     }
-    
+
     private class FilterListener implements ActionListener<Filter.FilterEvent> {
         @Override
-        public void actionPerformed(ActionEvent<FilterEvent> actionEvent) {
+        public void actionPerformed(final ActionEvent<FilterEvent> actionEvent) {
             SwingUtilities.invokeLater(new Runnable() {
                 @Override
                 public void run() {
+                    Object payload = actionEvent.getPayload();
+                    if (payload == null) {
+                        filterMap.clear();
+                    } else if (payload instanceof FilterRefRemovedPayload) {
+                        FilterRefRemovedPayload<?> refRemovedPayload =
+                                (FilterRefRemovedPayload) payload;
+                        filterMap.remove(refRemovedPayload.getRef());
+                    }
                     rebuildTree();
                 }
             });
         }
     }
-    
+
     private class RefPayload<R extends Ref> {
         R reference;
         boolean expanded;
     }
-    
+
     // decorator accessors
 
     public DecoratorManager getDecoratorManager() {
@@ -363,12 +386,14 @@
     }
 
     public void addFilter(ReferenceFilter filter) {
+        filterMap.clear();
         filters.add(filter);
         filter.addFilterEventListener(filterListener);
         rebuildTree();
     }
 
     public void removeFilter(ReferenceFilter filter) {
+        filterMap.clear();
         filters.remove(filter);
         filter.removeFilterEventListener(filterListener);
         rebuildTree();
@@ -382,5 +407,28 @@
             }
         });
     }
+
+    protected static abstract class FilterPayload<T extends Ref> {
+
+        private final T ref;
+
+        public FilterPayload(T ref) {
+            this.ref = ref;
+        }
+
+        public T getRef() {
+            return ref;
+        }
+
+    }
+
+    public static class FilterRefAddedPayload {}
+
+    public static class FilterRefRemovedPayload<T extends Ref> extends FilterPayload<T> {
+        public FilterRefRemovedPayload(T ref) {
+            super(ref);
+        }
+    }
+
 }
 
--- a/client/swing/src/main/resources/com/redhat/thermostat/client/swing/internal/strings.properties	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/main/resources/com/redhat/thermostat/client/swing/internal/strings.properties	Tue Feb 09 12:39:50 2016 -0500
@@ -11,4 +11,7 @@
 
 CUT = Cut
 COPY = Copy
-PASTE = Paste
\ No newline at end of file
+PASTE = Paste
+
+SHOW_BUTTON_TEXT = Show button text
+HIDE_BUTTON_TEXT = Hide button text
Binary file client/swing/src/main/resources/fontawesome-webfont.ttf has changed
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ActionButtonTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -113,13 +113,11 @@
 
                     @Override
                     public int getIconWidth() {
-                        // TODO Auto-generated method stub
                         return 16;
                     }
 
                     @Override
                     public int getIconHeight() {
-                        // TODO Auto-generated method stub
                         return 16;
                     }
                 };
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/FontAwesomeIconTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/FontAwesomeIconTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,32 +36,29 @@
 
 package com.redhat.thermostat.client.swing.components;
 
-import static org.junit.Assert.*;
+import org.junit.Test;
 
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.image.BufferedImage;
 
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
-import com.redhat.thermostat.annotations.internal.CacioTest;
-
-@Category(CacioTest.class)
 public class FontAwesomeIconTest {
 
     @Test
     public void testIcon() {
-        FontAwesomeIcon icon = new FontAwesomeIcon('\uf004', 24);
+        FontAwesomeIcon icon = new FontAwesomeIcon('\uf004', 20);
         
-        assertEquals(24, icon.getIconHeight());
-        assertEquals(24, icon.getIconWidth());
+        assertEquals(20, icon.getIconHeight());
+        assertEquals(20, icon.getIconWidth());
         
-        BufferedImage buffer = new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB);
+        BufferedImage buffer = new BufferedImage(25, 25, BufferedImage.TYPE_INT_ARGB);
         Graphics g = buffer.getGraphics();
         
         g.setColor(Color.WHITE);
-        g.fillRect(0, 0, 24, 24);
+        g.fillRect(0, 0, 25, 25);
         
         icon.paintIcon(null, g, 0, 0);
         
@@ -77,12 +74,19 @@
         rgb = buffer.getRGB(12, 12);
         assertEquals(0xFF000000, rgb);
         
-        rgb = buffer.getRGB(12, 21);
+        rgb = buffer.getRGB(10, 16);
+        assertEquals(0xFF000000, rgb);
+
+        // on the border of the image, this is some gray due to antialiasing
+        rgb = buffer.getRGB(10, 18);
+        assertFalse(0xFF000000 == rgb);
+        assertFalse(0xFFFFFFFF == rgb);
+
+        rgb = buffer.getRGB(10, 20);
         assertEquals(0xFFFFFFFF, rgb);
-        
-        rgb = buffer.getRGB(12, 20);
-        assertFalse(0xFFFFFFFF == rgb);
-        assertFalse(0xFF000000 == rgb);
+
+        rgb = buffer.getRGB(20, 20);
+        assertEquals(0xFFFFFFFF, rgb);
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/FontAwesomeIconTestManual.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components;
+
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+import java.awt.Color;
+import java.awt.Dimension;
+
+/**
+ * Manual Test for FontAwesomeIcon, it lets you display an icon to see if it's
+ * rendered correctly. If not, try adding debugging borders around the icon
+ * (in the drawing code) to see the proper bounds and alignment.
+ * If no icon is displayed check that the the fonts are loaded correctly and
+ * then that the chosen character is still present.
+ */
+public class FontAwesomeIconTestManual {
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                JFrame frame = new JFrame();
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                frame.setMinimumSize(new Dimension(600, 600));
+                javax.swing.Icon icon = new FontAwesomeIcon('\uf004', 250, new Color(168, 172, 168));
+                JLabel label = new JLabel(icon, JLabel.CENTER);
+                label.setBorder(new DebugBorder());
+                frame.add(label);
+
+                frame.setVisible(true);
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/HtmlShadowLabelTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components;
+
+import com.redhat.thermostat.annotations.internal.CacioTest;
+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.fixture.FrameFixture;
+import org.fest.swing.fixture.JLabelFixture;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import javax.swing.JFrame;
+import java.awt.FlowLayout;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+@Category(CacioTest.class)
+@RunWith(CacioFESTRunner.class)
+public class HtmlShadowLabelTest {
+
+    static final String VIEW_NAME = "HtmlShadowLabel";
+
+    private JFrame frame;
+    private FrameFixture frameFixture;
+    private HtmlShadowLabel label;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @Before
+    public void setUp() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                frame = new JFrame();
+                frame.setLayout(new FlowLayout());
+                label = new HtmlShadowLabel();
+                label.setName(VIEW_NAME);
+                frame.add(label);
+            }
+        });
+        frameFixture = new FrameFixture(frame);
+    }
+
+    @After
+    public void tearDown() {
+        frameFixture.cleanUp();
+        frameFixture = null;
+    }
+
+    @Category(GUITest.class)
+    @GUITest
+    @Test
+    public void testEmptyInput() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            public void executeInEDT() throws Throwable {
+                label.setText("");
+            }
+        });
+        frameFixture.show();
+        JLabelFixture textBox = frameFixture.label(VIEW_NAME);
+        assertThat(textBox.text(), is(equalTo("<html></html>")));
+    }
+
+    @Category(GUITest.class)
+    @GUITest
+    @Test
+    public void testCarriageReturnsStripped() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            public void executeInEDT() throws Throwable {
+                label.setText("test\rstring\r");
+            }
+        });
+        frameFixture.show();
+        JLabelFixture textBox = frameFixture.label(VIEW_NAME);
+        assertThat(textBox.text(), is(equalTo("<html>teststring</html>")));
+    }
+
+    @Category(GUITest.class)
+    @GUITest
+    @Test
+    public void testNewlineBecomesBR() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            public void executeInEDT() throws Throwable {
+                label.setText("this\ntext\nhas\nnewlines");
+            }
+        });
+        frameFixture.show();
+        JLabelFixture textBox = frameFixture.label(VIEW_NAME);
+        assertThat(textBox.text(), is(equalTo("<html>this<br>text<br>has<br>newlines</html>")));
+    }
+
+    @Category(GUITest.class)
+    @GUITest
+    @Test
+    public void testNonFancyInput() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            public void executeInEDT() throws Throwable {
+                label.setText("this is non-fancy input text");
+            }
+        });
+        frameFixture.show();
+        JLabelFixture textBox = frameFixture.label(VIEW_NAME);
+        assertThat(textBox.text(), is(equalTo("<html>this is non-fancy input text</html>")));
+    }
+
+    @Category(GUITest.class)
+    @GUITest
+    @Test
+    public void testSymbolsAreEscaped() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            public void executeInEDT() throws Throwable {
+                label.setText("<&>");
+            }
+        });
+        frameFixture.show();
+        JLabelFixture textBox = frameFixture.label(VIEW_NAME);
+        assertThat(textBox.text(), is(equalTo("<html>&lt;&amp;&gt;</html>")));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/ThermostatTableRendererTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.swing.Icon;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.DefaultListCellRenderer.UIResource;
+
+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.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.client.swing.IconResource;
+import com.redhat.thermostat.annotations.internal.CacioTest;
+
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+
+@Category(CacioTest.class)
+@RunWith(CacioFESTRunner.class)
+public class ThermostatTableRendererTest {
+
+    private Icon icon;
+
+    @BeforeClass
+    public static void setupOnce() {
+        FailOnThreadViolationRepaintManager.install();
+    }
+
+    @Before
+    public void setup() {
+        icon = new FontAwesomeIcon('\uf188', 30);
+    }
+
+    @GUITest
+    @Category(GUITest.class)
+    @Test
+    public void verifyIconIsSet() {
+        GuiActionRunner.execute(new GuiTask() {
+            @Override
+            protected void executeInEDT() throws Throwable {
+                JTable table = new JTable();
+                ThermostatTableRenderer renderer = new ThermostatTableRenderer();
+                JLabel comp = (JLabel) renderer.getTableCellRendererComponent(table, icon, true, true, 0, 0);
+                assertEquals(icon, comp.getIcon());
+            }
+        });
+    }
+
+}
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/RecentTimeControlPanelTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/RecentTimeControlPanelTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -47,7 +47,8 @@
 import javax.swing.JComboBox;
 
 import com.redhat.thermostat.annotations.internal.CacioTest;
-import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.common.Duration;
+
 import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
 import org.fest.swing.annotation.GUITest;
 import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
@@ -137,7 +138,8 @@
             @Override
             public void propertyChange(final PropertyChangeEvent evt) {
               Duration d = (Duration) evt.getNewValue();
-              if (d.unit.equals(TimeUnit.MINUTES) && d.value == 5) {
+              Duration expected = new Duration(5, TimeUnit.MINUTES);
+              if (expected.equals(d)) {
                   b[0] = true;
               }
             }
@@ -160,7 +162,8 @@
             @Override
             public void propertyChange(final PropertyChangeEvent evt) {
               Duration d = (Duration) evt.getNewValue();
-              if (d.unit.equals(TimeUnit.HOURS) && d.value == 10) {
+              Duration expected = new Duration(10, TimeUnit.HOURS);
+              if (d.equals(expected)) {
                   b[0] = true;
               }
             }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanelTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/SingleValueChartPanelTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -46,7 +46,8 @@
 import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.annotations.internal.CacioTest;
-import com.redhat.thermostat.client.core.experimental.Duration;
+import com.redhat.thermostat.common.Duration;
+
 import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
 import org.fest.swing.annotation.GUITest;
 import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
@@ -122,7 +123,8 @@
             @Override
             public void propertyChange(final PropertyChangeEvent evt) {
                 Duration d = (Duration) evt.getNewValue();
-                if (d.unit.equals(TimeUnit.MINUTES) && d.value == 5) {
+                Duration expected = new Duration(5, TimeUnit.MINUTES);
+                if (d.equals(expected)) {
                     b[0] = true;
                 }
             }
@@ -144,7 +146,8 @@
             @Override
             public void propertyChange(final PropertyChangeEvent evt) {
                 Duration d = (Duration) evt.getNewValue();
-                if (d.unit.equals(TimeUnit.HOURS) && d.value == 10) {
+                Duration expected = new Duration(10, TimeUnit.HOURS);
+                if (d.equals(expected)) {
                     b[0] = true;
                 }
             }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapComponentTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapComponentTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,18 +36,14 @@
 
 package com.redhat.thermostat.client.swing.components.experimental;
 
-import org.jfree.chart.renderer.category.GradientBarPainter;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
-import javax.swing.UIManager;
 import java.awt.BorderLayout;
 import java.awt.Font;
 import java.awt.event.ActionEvent;
@@ -58,13 +54,18 @@
 import java.util.List;
 import java.util.Random;
 
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
 
 public class TreeMapComponentTest {
 
@@ -521,6 +522,10 @@
                 modelA.addChild(new TreeMapNode("AA", 2.0));
                 modelA.addChild(new TreeMapNode("AB", 3.0));
 
+                final TreeMapNode modelC = new TreeMapNode("C", 1.0);
+                modelC.addChild(new TreeMapNode("CA", 2.0));
+                modelC.addChild(new TreeMapNode("CB", 3.0));
+
                 final TreeMapNode modelB = new TreeMapNode("B", 5.0);
                 final Random generator = new Random();
                 for (int i = 0; i < 100; i++) {
@@ -535,19 +540,29 @@
                 // FIXME this hack should not be needed
                 UIManager.put("thermostat-default-font", Font.decode(Font.MONOSPACED));
 
-                final TreeMapComponent treeMap = new TreeMapComponent();
-                treeMap.setToolTipRenderer(new WeightRenderer());
-                treeMap.setModel(modelA);
+                JPanel container = new JPanel(new BorderLayout());
+                final JTabbedPane tabbedPane = new JTabbedPane();
 
-                JPanel container = new JPanel(new BorderLayout());
+                final TreeMapComponent treeMapA = new TreeMapComponent();
+                treeMapA.setToolTipRenderer(new WeightRenderer());
+                treeMapA.setModel(modelA);
+
+                final TreeMapComponent treeMapC = new TreeMapComponent();
+                treeMapC.setToolTipRenderer(new WeightRenderer());
+                treeMapC.setModel(modelC);
 
                 JPanel buttonPanel = new JPanel();
                 JButton changeModelButton = new JButton("Change model");
                 changeModelButton.addActionListener(new ActionListener() {
                     @Override
                     public void actionPerformed(ActionEvent e) {
-                        TreeMapNode newModel = treeMap.getTreeMapRoot() == modelA ? modelB : modelA;
-                        treeMap.setModel(newModel);
+                        if (tabbedPane.getSelectedIndex() == 0) {
+                            TreeMapNode newModel = treeMapA.getTreeMapRoot() == modelA ? modelB : modelA;
+                            treeMapA.setModel(newModel);
+                        } else {
+                            TreeMapNode newModel = treeMapC.getTreeMapRoot() == modelC ? modelB : modelC;
+                            treeMapC.setModel(newModel);
+                        }
                     }
                 });
                 buttonPanel.add(changeModelButton);
@@ -556,10 +571,15 @@
                 addChildToRootButton.addActionListener(new ActionListener() {
                     @Override
                     public void actionPerformed(ActionEvent e) {
-                        TreeMapNode currentModel = treeMap.getTreeMapRoot();
-                        currentModel.addChild(new TreeMapNode("new", 5.0));
-
-                        treeMap.setModel(currentModel);
+                        if (tabbedPane.getSelectedIndex() == 0) {
+                            TreeMapNode currentModel = treeMapA.getTreeMapRoot();
+                            currentModel.addChild(new TreeMapNode("new", 5.0));
+                            treeMapA.setModel(currentModel);
+                        } else {
+                            TreeMapNode currentModel = treeMapC.getTreeMapRoot();
+                            currentModel.addChild(new TreeMapNode("new", 5.0));
+                            treeMapC.setModel(currentModel);
+                        }
                     }
                 });
                 buttonPanel.add(addChildToRootButton);
@@ -568,21 +588,34 @@
                 addRandomNodeButton.addActionListener(new ActionListener() {
                     @Override
                     public void actionPerformed(ActionEvent e) {
-                        TreeMapNode currentModel = treeMap.getTreeMapRoot();
-                        List<TreeMapNode> allNodes = new ArrayList<>();
-                        findAllNodes(allNodes, currentModel);
-                        TreeMapNode parent = allNodes.get(generator.nextInt(allNodes.size()));
-                        double weight = Math.pow(10, generator.nextInt(4));
-                        parent.addChild(new TreeMapNode("rand", weight));
+                        if (tabbedPane.getSelectedIndex() == 0) {
+                            TreeMapNode currentModel = treeMapA.getTreeMapRoot();
+                            List<TreeMapNode> allNodes = new ArrayList<>();
+                            findAllNodes(allNodes, currentModel);
+                            TreeMapNode parent = allNodes.get(generator.nextInt(allNodes.size()));
+                            double weight = Math.pow(10, generator.nextInt(4));
+                            parent.addChild(new TreeMapNode("rand", weight));
 
-                        treeMap.setModel(currentModel);
+                            treeMapA.setModel(currentModel);
+                        } else {
+                            TreeMapNode currentModel = treeMapC.getTreeMapRoot();
+                            List<TreeMapNode> allNodes = new ArrayList<>();
+                            findAllNodes(allNodes, currentModel);
+                            TreeMapNode parent = allNodes.get(generator.nextInt(allNodes.size()));
+                            double weight = Math.pow(10, generator.nextInt(4));
+                            parent.addChild(new TreeMapNode("rand", weight));
+
+                            treeMapC.setModel(currentModel);
+                        }
                     }
                 });
                 buttonPanel.add(addRandomNodeButton);
 
                 container.add(buttonPanel, BorderLayout.PAGE_START);
-                container.add(treeMap, BorderLayout.CENTER);
+                tabbedPane.addTab("TMA", treeMapA);
+                tabbedPane.addTab("TMC", treeMapC);
 
+                container.add(tabbedPane, BorderLayout.CENTER);
                 mainWindow.add(container, BorderLayout.CENTER);
 
                 mainWindow.setSize(500, 200);
@@ -594,7 +627,7 @@
     public static class WeightRenderer implements TreeMapComponent.ToolTipRenderer {
         @Override
         public String render(TreeMapNode node) {
-            return node.getLabel() + " RW:" + node.getRealWeight() + " W:" + node.getWeight();
+            return node.getLabel() + " RW:" + node.getRealWeight();
         }
     }
     interface KeyShortcutTestResultHandler {
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapNodeTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeMapNodeTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -125,13 +125,6 @@
     }
 
     @Test
-    public final void testGetSetWeight() {
-        assertEquals(1.0, node.getWeight(), DELTA);
-        node.setWeight(5);
-        assertEquals(5.0, node.getWeight(), DELTA);
-    }
-
-    @Test
     public final void testGetSetRectangle() {        
         Rectangle2D.Double r = new Rectangle2D.Double(5, 5, 5, 5);
         node.setRectangle(r);
@@ -157,18 +150,6 @@
     }
 
     @Test
-    public final void testAllowNonPositiveWeight() {
-        assertFalse(TreeMapNode.isAllowNonPositiveWeight());
-        node.setWeight(-5);
-        assertEquals(1.0, node.getWeight(), DELTA);
-        assertEquals(1.0, node.getRealWeight(), DELTA);
-
-        TreeMapNode.setAllowNonPositiveWeight(true);
-        node.setWeight(-5);
-        assertEquals(-5.0, node.getWeight(), DELTA);
-    }
-
-    @Test
     public final void testIsDrawable() {
         Rectangle2D.Double r = new Rectangle2D.Double(5, 5, 5, 5);
         node.setRectangle(r);
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeProcessorTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/experimental/TreeProcessorTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -154,12 +154,15 @@
         assertEquals(numSiblings, children.size());
         TreeMapNode.sort(children);
 
-        double weight = children.get(0).getWeight();
-        for(int i = 1; i < numSiblings - 1; i++) {
-            double currentWeight = children.get(i).getWeight();
-            //check that the ratio between node weights is approximately 2 (which is the BASE)
-            assertEquals(BASE, weight/currentWeight, DELTA);
-            weight = currentWeight;
+        Rectangle2D.Double referenceRectangle = children.get(0).getRectangle();
+        double referenceArea = referenceRectangle.getHeight() * referenceRectangle.getWidth();
+
+        for (int i = 1; i < numSiblings - 1; i++) {
+            Rectangle2D.Double currentRectangle = children.get(i).getRectangle();
+            double currentArea = currentRectangle.getHeight() * currentRectangle.getWidth();
+            //check that the ratio between areas is approximately 2 (which is the BASE)
+            assertEquals(BASE, referenceArea / currentArea, DELTA);
+            referenceArea = currentArea;
         }
     }
 }
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/MainWindowControllerImplTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.client.swing.internal;
 
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.isA;
@@ -46,8 +48,16 @@
 
 import java.io.File;
 import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
+import com.redhat.thermostat.client.swing.internal.search.ReferenceFieldSearchFilter;
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.VmRef;
 import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
 import org.junit.After;
 import org.junit.Before;
@@ -97,8 +107,6 @@
 
     private MainView view;
 
-    private Timer mainWindowTimer;
-
     private UriOpener uriOpener;
 
     private HostInfoDAO mockHostsDAO;
@@ -106,7 +114,6 @@
 
     private ReferenceFilterRegistry hostFilterRegistry;
 
-    private VMInformationRegistry vmInfoRegistry;
     private MenuRegistry menus;
 
     private StubBundleContext context;
@@ -119,12 +126,14 @@
     private DecoratorRegistryController decoratorController;
     
     private ContextActionController contextController;
-    
-    @BeforeClass
-    public static void setUpOnce() {
-        // TODO remove when controller uses mocked objects rather than real swing objects
-        FailOnThreadViolationRepaintManager.install();
-    }
+    private ReferenceFieldSearchFilter referenceFieldSearchFilter;
+    private HostMonitor hostMonitor;
+    private NetworkMonitor networkMonitor;
+    private HostRef host;
+    private List<HostRef> hosts;
+    private VmRef vm1;
+    private VmRef vm2;
+    private List<VmRef> vms;
 
     @SuppressWarnings({ "unchecked", "rawtypes" }) // ActionListener fluff
     @Before
@@ -134,10 +143,7 @@
         uriOpener = mock(UriOpener.class);
                 
         // Setup timers
-        mainWindowTimer = mock(Timer.class);
-        Timer otherTimer = mock(Timer.class); // FIXME needed for SummaryView; remove later
         TimerFactory timerFactory = mock(TimerFactory.class);
-        when(timerFactory.createTimer()).thenReturn(mainWindowTimer).thenReturn(otherTimer);
         ApplicationService appSvc = mock(ApplicationService.class);
         when (appSvc.getTimerFactory()).thenReturn(timerFactory);
 
@@ -159,8 +165,8 @@
         
         VersionAndInfoViewProvider summaryViewProvider = mock(VersionAndInfoViewProvider.class);
         context.registerService(VersionAndInfoViewProvider.class, summaryViewProvider, null);
-        VersionAndInfoView summaryView = mock(VersionAndInfoView.class);
-        when(summaryViewProvider.createView()).thenReturn(summaryView);
+        VersionAndInfoView versionAndInfoView = mock(VersionAndInfoView.class);
+        when(summaryViewProvider.createView()).thenReturn(versionAndInfoView);
         
         HostInformationViewProvider hostInfoViewProvider = mock(HostInformationViewProvider.class);
         context.registerService(HostInformationViewProvider.class, hostInfoViewProvider, null);
@@ -177,11 +183,20 @@
         ClientConfigViewProvider clientConfigViewProvider = mock(ClientConfigViewProvider.class);
         context.registerService(ClientConfigViewProvider.class, clientConfigViewProvider, null);
 
-        HostMonitor hostMonitor = mock(HostMonitor.class);
+        hostMonitor = mock(HostMonitor.class);
         context.registerService(HostMonitor.class, hostMonitor, null);
-        NetworkMonitor networkMonitor = mock(NetworkMonitor.class);
+        networkMonitor = mock(NetworkMonitor.class);
         context.registerService(NetworkMonitor.class, networkMonitor, null);
 
+        host = mock(HostRef.class);
+        hosts = Collections.singletonList(host);
+        when(networkMonitor.getHosts(any(Filter.class))).thenReturn(hosts);
+
+        vm1 = mock(VmRef.class);
+        vm2 = mock(VmRef.class);
+        vms = Arrays.asList(vm1, vm2);
+        when(hostMonitor.getVirtualMachines(any(HostRef.class), any(Filter.class))).thenReturn(vms);
+
         // Setup View
         view = mock(MainView.class);
         ArgumentCaptor<ActionListener> grabListener = ArgumentCaptor.forClass(ActionListener.class);
@@ -189,6 +204,9 @@
         
         contextController = mock(ContextActionController.class);
         when(view.getContextActionController()).thenReturn(contextController);
+
+        referenceFieldSearchFilter = mock(ReferenceFieldSearchFilter.class);
+        when(view.getSearchFilter()).thenReturn(referenceFieldSearchFilter);
         
         treeController = mock(HostTreeController.class);
         ArgumentCaptor<ActionListener> hostTreeCaptor = ArgumentCaptor.forClass(ActionListener.class);
@@ -202,7 +220,6 @@
         RegistryFactory registryFactory = mock(RegistryFactory.class);
         hostFilterRegistry = mock(ReferenceFilterRegistry.class);
 
-        vmInfoRegistry = mock(VMInformationRegistry.class);
         menus = mock(MenuRegistry.class);
         shutdown = mock(CountDownLatch.class);
 
@@ -210,15 +227,11 @@
         
         when(registryFactory.createMenuRegistry()).thenReturn(menus);
         when(registryFactory.createFilterRegistry()).thenReturn(hostFilterRegistry);
-        when(registryFactory.createVMInformationRegistry()).thenReturn(vmInfoRegistry);
         when(registryFactory.createDecoratorController()).thenReturn(decoratorController);
         
         ArgumentCaptor<ActionListener> grabHostFiltersListener = ArgumentCaptor.forClass(ActionListener.class);
         doNothing().when(hostFilterRegistry).addActionListener(grabHostFiltersListener.capture());
 
-        ArgumentCaptor<ActionListener> grabInfoRegistry = ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(vmInfoRegistry).addActionListener(grabInfoRegistry.capture());
-
         controller = new MainWindowControllerImpl(context, appSvc, view, registryFactory, shutdown, uriOpener);
         
         l = grabListener.getValue();
@@ -244,10 +257,15 @@
         verify(decoratorController, times(1)).stop();
     }
 
-    @Test
+    @Test @SuppressWarnings("unchecked")
     public void verifyShowMainWindowActuallyCallsView() {
         controller.showMainMainWindow();
         verify(view).showMainWindow();
+        verify(referenceFieldSearchFilter).addHosts(any(Collection.class));
+        verify(referenceFieldSearchFilter).addVMs(eq(host), any(Collection.class));
+        verify(treeController).registerHost(host);
+        verify(treeController).registerVM(vm1);
+        verify(treeController).registerVM(vm2);
     }
 
     @Test
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/registry/decorator/DecoratorRegistryControllerTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/registry/decorator/DecoratorRegistryControllerTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,10 +36,16 @@
 
 package com.redhat.thermostat.client.swing.internal.registry.decorator;
 
+import static org.hamcrest.core.IsAnything.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
 
+import com.redhat.thermostat.client.ui.Decorator;
+import com.redhat.thermostat.client.ui.ToggleableReferenceFieldLabelDecorator;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ThermostatExtensionRegistry;
 import org.junit.Before;
 import org.junit.Test;
 import org.osgi.framework.InvalidSyntaxException;
@@ -48,6 +54,8 @@
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.HostTreeController;
 import com.redhat.thermostat.client.swing.internal.vmlist.controller.DecoratorListener;
 
+import java.util.Collections;
+
 /**
  *
  */
@@ -114,18 +122,23 @@
         controller.start();
         
         verify(infoLabel).addActionListener(l0);
+        verify(infoLabel).addActionListener(controller.getToggleableFilterListener());
         verify(infoLabel).start();
         
         verify(mainLabel).addActionListener(l1);
+        verify(mainLabel).addActionListener(controller.getToggleableFilterListener());
         verify(mainLabel).start();
         
         verify(mainLabel).addActionListener(l1);
+        verify(mainLabel).addActionListener(controller.getToggleableFilterListener());
         verify(mainLabel).start();
         
         verify(mainLabel).addActionListener(l1);
+        verify(mainLabel).addActionListener(controller.getToggleableFilterListener());
         verify(mainLabel).start();
         
         verify(icon).addActionListener(l2);
+        verify(icon).addActionListener(controller.getToggleableFilterListener());
         verify(icon).start();
     }
 
@@ -134,26 +147,87 @@
         DecoratorRegistryController controller =
                 new DecoratorRegistryController(registryFactory);
         controller.init(hostController);
-        
+
         // since it's all mocked, we don't need to start anything
         // controller.start();
-        
+
         controller.stop();
 
         verify(infoLabel).removeActionListener(l0);
+        verify(infoLabel).removeActionListener(controller.getToggleableFilterListener());
         verify(infoLabel).stop();
-        
+
         verify(mainLabel).removeActionListener(l1);
+        verify(mainLabel).removeActionListener(controller.getToggleableFilterListener());
         verify(mainLabel).stop();
-        
+
         verify(mainLabel).removeActionListener(l1);
+        verify(mainLabel).removeActionListener(controller.getToggleableFilterListener());
+        verify(mainLabel).stop();
+
+        verify(mainLabel).removeActionListener(l1);
+        verify(mainLabel).removeActionListener(controller.getToggleableFilterListener());
         verify(mainLabel).stop();
-        
-        verify(mainLabel).removeActionListener(l1);
-        verify(mainLabel).stop();
-        
+
         verify(icon).removeActionListener(l2);
+        verify(icon).removeActionListener(controller.getToggleableFilterListener());
         verify(icon).stop();
-    } 
+    }
+
+    @Test @SuppressWarnings("unchecked")
+    public void testToggleableDecoratorFilterListenerOnServiceAdded() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> statusEventActionListener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        ToggleableReferenceFieldLabelDecorator decorator = mock(ToggleableReferenceFieldLabelDecorator.class);
+
+        ActionEvent<ThermostatExtensionRegistry.Action> event =
+                new ActionEvent<>(decorator, ThermostatExtensionRegistry.Action.SERVICE_ADDED);
+        event.setPayload(decorator);
+
+        DecoratorRegistryController.ToggleableDecoratorFilterListener filterListener =
+                new DecoratorRegistryController.ToggleableDecoratorFilterListener(statusEventActionListener);
+
+        filterListener.actionPerformed(event);
+        verify(decorator).addStatusEventListener(statusEventActionListener);
+    }
+
+    @Test @SuppressWarnings("unchecked")
+    public void testToggleableDecoratorFilterListenerOnServiceRemoved() {
+        ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent> statusEventActionListener =
+                (ActionListener<ToggleableReferenceFieldLabelDecorator.StatusEvent>) mock(ActionListener.class);
+
+        ToggleableReferenceFieldLabelDecorator decorator = mock(ToggleableReferenceFieldLabelDecorator.class);
+
+        ActionEvent<ThermostatExtensionRegistry.Action> event =
+                new ActionEvent<>(decorator, ThermostatExtensionRegistry.Action.SERVICE_REMOVED);
+        event.setPayload(decorator);
+
+        DecoratorRegistryController.ToggleableDecoratorFilterListener filterListener =
+                new DecoratorRegistryController.ToggleableDecoratorFilterListener(statusEventActionListener);
+
+        filterListener.actionPerformed(event);
+        verify(decorator).removeStatusEventListener(statusEventActionListener);
+    }
+
+    @Test @SuppressWarnings("unchecked")
+    public void testToggleStatusListener() {
+        DecoratorListener<ToggleableReferenceFieldLabelDecorator> decoratorListener =
+                (DecoratorListener<ToggleableReferenceFieldLabelDecorator>) mock(DecoratorListener.class);
+
+        DecoratorRegistryController.ToggleStatusListener toggleStatusListener =
+                new DecoratorRegistryController.ToggleStatusListener(
+                        Collections.<DecoratorListener<? extends Decorator>>singleton(decoratorListener)
+                );
+
+        ActionEvent<ToggleableReferenceFieldLabelDecorator.StatusEvent> event =
+                new ActionEvent<>(decoratorListener, ToggleableReferenceFieldLabelDecorator.StatusEvent.STATUS_CHANGED);
+        event.setPayload(decoratorListener);
+
+        toggleStatusListener.actionPerformed(event);
+
+        verify(decoratorListener).fireDecorationChanged();
+    }
+
 }
 
--- a/common/command/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/command/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-common</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-common-command</artifactId>
--- a/common/core/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/core/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-common</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-common-core</artifactId>
--- a/common/core/src/main/java/com/redhat/thermostat/common/Clock.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Clock.java	Tue Feb 09 12:39:50 2016 -0500
@@ -36,11 +36,15 @@
 
 package com.redhat.thermostat.common;
 
+import java.text.DateFormat;
+
 /**
  * A clock. Useful for separating the source of time from a piece of code.
  */
 public interface Clock {
 
+    DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.LONG);
+
     /**
      * Returns the current real time in milliseconds (measured since the UNIX epoch).
      */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Duration.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012-2015 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+public class Duration {
+
+    private final int value;
+    private final TimeUnit unit;
+
+    public Duration(int value, TimeUnit unit) {
+        this.value = value;
+        this.unit = unit;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public TimeUnit getUnit() {
+        return unit;
+    }
+
+    public long asMilliseconds() {
+        return unit.toMillis(value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj.getClass() != this.getClass()) {
+            return false;
+        }
+
+        Duration other = (Duration) obj;
+        return Objects.equals(value, other.value) && Objects.equals(unit, other.unit);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value, unit);
+    }
+
+    @Override
+    public String toString() {
+        return value + " " + unit;
+    }
+}
\ No newline at end of file
--- a/common/core/src/main/java/com/redhat/thermostat/common/Filter.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Filter.java	Tue Feb 09 12:39:50 2016 -0500
@@ -97,6 +97,10 @@
      */
     protected void notify(FilterEvent action) {
         notifier.fireAction(action);
-    }    
+    }
+
+    protected void notify(FilterEvent action, Object payload) {
+        notifier.fireAction(action, payload);
+    }
 }
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/Ordered.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/Ordered.java	Tue Feb 09 12:39:50 2016 -0500
@@ -72,6 +72,13 @@
      * or VM's threads.
      */
     public static final int ORDER_THREAD_GROUP = 500;
+
+    /**
+     * Order group for services that provide information about code running in a
+     * Host or a VM.
+     */
+    public static final int ORDER_CODE_GROUP = 600;
+
     /**
      * Order group for user-defined services. This should always be
      * the last order group.
--- a/common/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-common</artifactId>
--- a/common/test/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/test/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-common</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-common-test</artifactId>
--- a/common/test/src/main/java/com/redhat/thermostat/testutils/Asserts.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/Asserts.java	Tue Feb 09 12:39:50 2016 -0500
@@ -61,5 +61,11 @@
             throw new AssertionError("Command " + name + " is registered but should not be");
         }
     }
+
+    public static <T, U extends T> void assertServiceIsRegistered(StubBundleContext context, Class<T> service, Class<U> implementation) {
+        if (!(context.isServiceRegistered(service.getName(), implementation))) {
+            throw new AssertionError("Service " + implementation.getName() + " is not registered under the API " + service.getName());
+        }
+    }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/DataObjectTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2015 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.testutils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+public abstract class DataObjectTest {
+
+    @Test
+    public void testBasicInstantiation() {
+        ArrayList<Class<?>> failureClasses = new ArrayList<>();
+        for (Class<?> clazz : getDataClasses()) {
+            try {
+                // pojo converters use this
+                clazz.newInstance();
+
+                // pojo converters fail at runtime if the constructor is not public
+                if (!Modifier.isPublic(clazz.getConstructor().getModifiers())) {
+                    throw new IllegalAccessError("constructor is not public");
+                }
+            } catch (ReflectiveOperationException e) {
+                failureClasses.add(clazz);
+            }
+        }
+        String msg = "Should be able to instantiate class using no-arg constructor: "
+                + failureClasses;
+        assertEquals(msg, 0, failureClasses.size());
+    }
+
+    /**
+     * Returns a list of classes representing data objects. These classes are
+     * checked to make sure they comply with the requirements for use as
+     * data-storage objects.
+     */
+    public abstract Class<?>[] getDataClasses();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/ServiceLoaderTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2012-2015 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.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import org.junit.Test;
+
+public abstract class ServiceLoaderTest<T> {
+
+    public static final int NO_EXTRA_SERVICES = 0;
+    public static final int STORAGE_SERVICES = 1;
+
+    private final int extraServices;
+    private final Class<T> interfaceClass;
+    private final List<Class<? extends T>> implementations;
+
+    /**
+     * @param type
+     *            the type of service, such as CategoryRegistration
+     * @param extraServices
+     *            the number of extra services. See the various *_SERVICES
+     *            constants.
+     * @param implementations
+     *            the implementations that are expected to be present. This is
+     *            not exclusive; additional services may be present.
+     */
+    @SafeVarargs
+    public ServiceLoaderTest(Class<T> type, int extraServices, Class<? extends T>... implementations) {
+        if (implementations == null || implementations.length == 0) {
+            throw new IllegalArgumentException("implementations is empty");
+        }
+        this.extraServices = extraServices;
+        this.interfaceClass = type;
+        this.implementations = Arrays.asList(implementations);
+    }
+
+    /*
+     * The web storage end-point uses service loader in order to determine the
+     * list of trusted/known categories/statements. This test is to ensure
+     * service loading works for this module's regs. E.g. renaming of the impl
+     * class without changing
+     * META-INF/com.redhat.thermostat.storage.core.auth.CategoryRegistration
+     */
+    @Test
+    public void serviceLoaderCanLoadService() {
+        Map<String, Boolean> foundServiceNames = new HashMap<>();
+        Class<? extends T> firstClass = implementations.get(0);
+        Collection<String> expectedClassNames = new HashSet<>();
+
+        for (Class<? extends T> klass : implementations) {
+            expectedClassNames.add(klass.getName());
+        }
+
+        ServiceLoader<T> loader = ServiceLoader.load(this.interfaceClass, firstClass.getClassLoader());
+
+        List<T> registrations = new ArrayList<>(1);
+        for (T r: loader) {
+            foundServiceNames.put(r.getClass().getName(), true);
+            registrations.add(r);
+        }
+
+        for (String className : expectedClassNames) {
+            assertTrue(foundServiceNames.containsKey(className));
+        }
+
+        int expectedServicesCount = this.extraServices + implementations.size();
+        assertEquals(registrations.size(), expectedServicesCount);
+    }
+
+}
--- a/common/test/src/main/java/com/redhat/thermostat/testutils/StubBundleContext.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/common/test/src/main/java/com/redhat/thermostat/testutils/StubBundleContext.java	Tue Feb 09 12:39:50 2016 -0500
@@ -302,11 +302,11 @@
 
     @Override
     public Filter createFilter(String filter) throws InvalidSyntaxException {
-        // FIXME this will break service trackers if FrameworkUtil is mocked.
-        // The following call will return null if FrameworkUtil is mocked.
-        // that's a problem because this is meant to be used (mostly) in test
-        // environments and that's where FrameworkUtil is likely to be mocked.
-        return FrameworkUtil.createFilter(filter);
+        Filter result = FrameworkUtil.createFilter(filter);
+        if (result == null) {
+            throw new AssertionError("FrameworkUtil created a null filter. Is it mocked incompletely?");
+        }
+        return result;
     }
 
     @Override
--- a/config/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/config/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <artifactId>thermostat</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-shared-config</artifactId>
--- a/dev/archetype-ext/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/archetype-ext/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-maven-archetype-ext</artifactId>
--- a/dev/archetype-ext/src/main/resources/META-INF/maven/archetype-metadata.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/archetype-ext/src/main/resources/META-INF/maven/archetype-metadata.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -46,7 +46,7 @@
       <defaultValue>The name of your Thermostat extension goes here</defaultValue>
     </requiredProperty>
     <requiredProperty key="thermostatVersion">
-      <defaultValue>1.99.6-SNAPSHOT</defaultValue>
+      <defaultValue>1.99.8-SNAPSHOT</defaultValue>
     </requiredProperty>
   </requiredProperties>
 
--- a/dev/data-populator/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/data-populator/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-data-populator</artifactId>
--- a/dev/ide-launcher/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/ide-launcher/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-ide-launcher</artifactId>
--- a/dev/multi-module-plugin-archetype/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/multi-module-plugin-archetype/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-maven-archetype-multimodule</artifactId>
--- a/dev/multi-module-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/multi-module-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <requiredProperties>
     <requiredProperty key="thermostat-core-version">
-      <defaultValue>1.99.6-SNAPSHOT</defaultValue>
+      <defaultValue>1.99.8-SNAPSHOT</defaultValue>
     </requiredProperty>
     <requiredProperty key="pluginDescription">
       <defaultValue>Thermostat example plugin</defaultValue>
--- a/dev/perflog-analyzer/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/perflog-analyzer/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-perflog-analyzer</artifactId>
--- a/dev/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-devel-modules</artifactId>
--- a/dev/schema-info-command/command/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/schema-info-command/command/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-schema-info</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   
   <artifactId>thermostat-schema-info-command</artifactId>
--- a/dev/schema-info-command/distribution/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/schema-info-command/distribution/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-schema-info</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-schema-info-distribution</artifactId>
--- a/dev/schema-info-command/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/dev/schema-info-command/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat-devel-modules</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   <artifactId>thermostat-schema-info</artifactId>
   <packaging>pom</packaging>
--- a/distribution/assembly/all-plugin-assembly.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/assembly/all-plugin-assembly.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -58,7 +58,9 @@
         <include>com.redhat.thermostat:thermostat-thread-distribution</include>
         <include>com.redhat.thermostat:thermostat-validate-distribution</include>
         <include>com.redhat.thermostat:thermostat-setup-distribution</include>
+        <include>com.redhat.thermostat:thermostat-local-distribution</include>
         <include>com.redhat.thermostat:thermostat-vm-classstat-distribution</include>
+        <include>com.redhat.thermostat:thermostat-vm-compiler-distribution</include>
         <include>com.redhat.thermostat:thermostat-vm-cpu-distribution</include>
         <include>com.redhat.thermostat:thermostat-vm-find-distribution</include>
         <include>com.redhat.thermostat:thermostat-vm-gc-distribution</include>
@@ -72,6 +74,8 @@
         <include>com.redhat.thermostat:thermostat-web-endpoint-distribution</include>
         <include>com.redhat.thermostat:thermostat-killvm-distribution</include>
         <include>com.redhat.thermostat:thermostat-vm-numa-distribution</include>
+        <include>com.redhat.thermostat:thermostat-platform-distribution</include>
+        <include>com.redhat.thermostat:thermostat-platform-swing-distribution</include>
       </includes>
     </dependencySet>  
   </dependencySets>
--- a/distribution/assembly/core-assembly.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/assembly/core-assembly.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -73,6 +73,7 @@
         <include>com.redhat.thermostat:thermostat-system-backend</include>
         <include>com.redhat.thermostat:thermostat-laf-utils</include>
         <include>org.osgi:org.osgi.compendium</include>
+        <include>org.apache:org.apache.felix.scr</include>
       </includes>
       <excludes>
         <exclude>org.osgi:org.osgi.core</exclude>
--- a/distribution/config/osgi-export.properties	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/config/osgi-export.properties	Tue Feb 09 12:39:50 2016 -0500
@@ -98,4 +98,9 @@
 javax.swing.plaf.metal
 javax.swing.table
 javax.swing.text
+javax.swing.text.html
 javax.swing.tree
+javax.security.sasl
+javax.xml.namespace
+javax.xml.transform.dom
+javax.xml.xpath
--- a/distribution/packaging/fedora/0001_shared_fix_bundle_loading.patch	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/packaging/fedora/0001_shared_fix_bundle_loading.patch	Tue Feb 09 12:39:50 2016 -0500
@@ -159,7 +159,7 @@
 diff --git a/distribution/scripts/thermostat-command-channel b/distribution/scripts/thermostat-command-channel
 --- a/distribution/scripts/thermostat-command-channel
 +++ b/distribution/scripts/thermostat-command-channel
-@@ -61,7 +61,7 @@
+@@ -54,7 +54,7 @@
  BOOT_CLASSPATH="${BOOT_CLASSPATH}:${THERMOSTAT_LIBS}/thermostat-agent-command-@project.version@.jar"
  BOOT_CLASSPATH="${BOOT_CLASSPATH}:${THERMOSTAT_LIBS}/thermostat-common-command-@project.version@.jar"
  BOOT_CLASSPATH="${BOOT_CLASSPATH}:${THERMOSTAT_LIBS}/thermostat-agent-command-server-@project.version@.jar"
@@ -187,12 +187,12 @@
 diff --git a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties
 --- a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties
 +++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties
-@@ -5,4 +5,5 @@
-         thermostat-plugin-validator-${project.version}.jar, \
-         thermostat-launcher-${project.version}.jar, \
+@@ -7,3 +7,6 @@
          jline-${jline.version}.jar, \
--        commons-cli-${commons-cli.version}.jar
-+        commons-cli-${commons-cli.version}.jar, \
+         commons-cli-${commons-cli.version}.jar, \
+         org.apache.felix.scr-${felix.scr.version}.jar, \
++        org.osgi.compendium-${osgi.compendium.version}.jar, \
++        kxml2-${kxml2.version}.jar, \
 +        jansi-${jansi.version}.jar
 diff --git a/setup/distribution/thermostat-plugin.xml b/setup/distribution/thermostat-plugin.xml
 --- a/setup/distribution/thermostat-plugin.xml
--- a/distribution/packaging/fedora/0002_shared_osgi_spec_fixes.patch	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/packaging/fedora/0002_shared_osgi_spec_fixes.patch	Tue Feb 09 12:39:50 2016 -0500
@@ -482,7 +482,7 @@
 diff --git a/keyring/pom.xml b/keyring/pom.xml
 --- a/keyring/pom.xml
 +++ b/keyring/pom.xml
-@@ -146,14 +146,8 @@
+@@ -170,14 +170,8 @@
      </dependency>
      
      <dependency>
@@ -588,6 +588,20 @@
      </dependency>
      <dependency>
        <groupId>com.redhat.thermostat</groupId>
+diff --git a/main/pom.xml b/main/pom.xml
+--- a/main/pom.xml
++++ b/main/pom.xml
+@@ -81,10 +81,6 @@
+       <artifactId>org.apache.felix.scr</artifactId>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
+-    </dependency>
+-    <dependency>
+       <groupId>net.sf.kxml</groupId>
+       <artifactId>kxml2</artifactId>
+     </dependency>
 diff --git a/notes/client-swing/pom.xml b/notes/client-swing/pom.xml
 --- a/notes/client-swing/pom.xml
 +++ b/notes/client-swing/pom.xml
@@ -703,10 +717,85 @@
      </dependency>
      <dependency>
        <groupId>com.redhat.thermostat</groupId>
+diff --git a/platform/beans/pom.xml b/platform/beans/pom.xml
+--- a/platform/beans/pom.xml
++++ b/platform/beans/pom.xml
+@@ -59,9 +59,8 @@
+     </dependency>
+ 
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+     </dependency>
+ 
+       <dependency>
+diff --git a/platform/collections/pom.xml b/platform/collections/pom.xml
+--- a/platform/collections/pom.xml
++++ b/platform/collections/pom.xml
+@@ -68,9 +68,8 @@
+         </dependency>
+ 
+         <dependency>
+-            <groupId>org.osgi</groupId>
+-            <artifactId>org.osgi.core</artifactId>
+-            <scope>provided</scope>
++          <groupId>org.apache.felix</groupId>
++          <artifactId>org.apache.felix.framework</artifactId>
+         </dependency>
+ 
+         <!-- declarative services -->
+diff --git a/platform/core/pom.xml b/platform/core/pom.xml
+--- a/platform/core/pom.xml
++++ b/platform/core/pom.xml
+@@ -61,9 +61,8 @@
+         </dependency>
+ 
+         <dependency>
+-            <groupId>org.osgi</groupId>
+-            <artifactId>org.osgi.core</artifactId>
+-            <scope>provided</scope>
++          <groupId>org.apache.felix</groupId>
++          <artifactId>org.apache.felix.framework</artifactId>
+         </dependency>
+ 
+         <dependency>
+diff --git a/platform/swing/core/pom.xml b/platform/swing/core/pom.xml
+--- a/platform/swing/core/pom.xml
++++ b/platform/swing/core/pom.xml
+@@ -67,9 +67,8 @@
+         </dependency>
+ 
+         <dependency>
+-            <groupId>org.osgi</groupId>
+-            <artifactId>org.osgi.core</artifactId>
+-            <scope>provided</scope>
++          <groupId>org.apache.felix</groupId>
++          <artifactId>org.apache.felix.framework</artifactId>
+         </dependency>
+ 
+         <dependency>
+diff --git a/platform/swing/widgets/pom.xml b/platform/swing/widgets/pom.xml
+--- a/platform/swing/widgets/pom.xml
++++ b/platform/swing/widgets/pom.xml
+@@ -67,9 +67,8 @@
+         </dependency>
+ 
+         <dependency>
+-            <groupId>org.osgi</groupId>
+-            <artifactId>org.osgi.core</artifactId>
+-            <scope>provided</scope>
++          <groupId>org.apache.felix</groupId>
++          <artifactId>org.apache.felix.framework</artifactId>
+         </dependency>
+ 
+         <dependency>
 diff --git a/pom.xml b/pom.xml
 --- a/pom.xml
 +++ b/pom.xml
-@@ -197,14 +197,26 @@
+@@ -204,14 +204,26 @@
      <lucene-analysis.bundle.symbolic-name>org.apache.servicemix.bundles.lucene-analyzers-common</lucene-analysis.bundle.symbolic-name>
      <lucene-core.bundle.symbolic-name>org.apache.servicemix.bundles.lucene</lucene-core.bundle.symbolic-name>
      <!--
@@ -735,9 +824,9 @@
      <osgi.compendium.bundle.symbolic-name>osgi.cmpn</osgi.compendium.bundle.symbolic-name>
 -    <felix.framework.version>4.2.0</felix.framework.version>
  
-     <netty.version>3.2.4.Final</netty.version>
-     <httpcomponents.core.version>4.3.2</httpcomponents.core.version>
-@@ -568,16 +580,6 @@
+     <felix.scr.version>1.8.2</felix.scr.version>
+     <felix.scr.annotations.version>1.9.12</felix.scr.annotations.version>
+@@ -585,16 +597,6 @@
        </dependency>
  
        <dependency>
@@ -851,7 +940,7 @@
 diff --git a/thread/collector/pom.xml b/thread/collector/pom.xml
 --- a/thread/collector/pom.xml
 +++ b/thread/collector/pom.xml
-@@ -85,14 +85,8 @@
+@@ -91,14 +91,8 @@
      </dependency>
  
      <dependency>
@@ -988,6 +1077,82 @@
      </dependency>
      <dependency>
        <groupId>com.redhat.thermostat</groupId>
+diff --git a/vm-compiler/agent/pom.xml b/vm-compiler/agent/pom.xml
+--- a/vm-compiler/agent/pom.xml
++++ b/vm-compiler/agent/pom.xml
+@@ -80,13 +80,8 @@
+       <scope>test</scope>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
+-    </dependency>
+-    <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+       <scope>provided</scope>
+     </dependency>
+     <dependency>
+diff --git a/vm-compiler/client-core/pom.xml b/vm-compiler/client-core/pom.xml
+--- a/vm-compiler/client-core/pom.xml
++++ b/vm-compiler/client-core/pom.xml
+@@ -99,13 +99,8 @@
+       <scope>test</scope>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
+-    </dependency>
+-    <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+       <scope>provided</scope>
+     </dependency>
+     <dependency>
+diff --git a/vm-compiler/client-swing/pom.xml b/vm-compiler/client-swing/pom.xml
+--- a/vm-compiler/client-swing/pom.xml
++++ b/vm-compiler/client-swing/pom.xml
+@@ -87,13 +87,8 @@
+       <scope>test</scope>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
+-    </dependency>
+-    <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+       <scope>provided</scope>
+     </dependency>
+     <dependency>
+diff --git a/vm-compiler/common/pom.xml b/vm-compiler/common/pom.xml
+--- a/vm-compiler/common/pom.xml
++++ b/vm-compiler/common/pom.xml
+@@ -99,13 +99,8 @@
+       <scope>test</scope>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
+-    </dependency>
+-    <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+       <scope>provided</scope>
+     </dependency>
+     <dependency>
 diff --git a/vm-cpu/agent/pom.xml b/vm-cpu/agent/pom.xml
 --- a/vm-cpu/agent/pom.xml
 +++ b/vm-cpu/agent/pom.xml
@@ -1088,6 +1253,25 @@
      </dependency>
      <dependency>
        <groupId>com.redhat.thermostat</groupId>
+diff --git a/vm-find/command/pom.xml b/vm-find/command/pom.xml
+--- a/vm-find/command/pom.xml
++++ b/vm-find/command/pom.xml
+@@ -68,13 +68,8 @@
+       <version>${project.version}</version>
+     </dependency>
+     <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.core</artifactId>
+-      <scope>provided</scope>
+-    </dependency>
+-    <dependency>
+-      <groupId>org.osgi</groupId>
+-      <artifactId>org.osgi.compendium</artifactId>
++      <groupId>org.apache.felix</groupId>
++      <artifactId>org.apache.felix.framework</artifactId>
+       <scope>provided</scope>
+     </dependency>
+     <dependency>
 diff --git a/vm-gc/agent/pom.xml b/vm-gc/agent/pom.xml
 --- a/vm-gc/agent/pom.xml
 +++ b/vm-gc/agent/pom.xml
@@ -1353,7 +1537,7 @@
 diff --git a/vm-heap-analysis/common/pom.xml b/vm-heap-analysis/common/pom.xml
 --- a/vm-heap-analysis/common/pom.xml
 +++ b/vm-heap-analysis/common/pom.xml
-@@ -93,14 +93,8 @@
+@@ -99,14 +99,8 @@
        <scope>test</scope>
      </dependency>
      <dependency>
--- a/distribution/packaging/fedora/0003_rhel_lucene_4.patch	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/packaging/fedora/0003_rhel_lucene_4.patch	Tue Feb 09 12:39:50 2016 -0500
@@ -4,7 +4,7 @@
 @@ -192,8 +192,8 @@
      <commons-fileupload.version>1.2.2</commons-fileupload.version>
  
-     <jline.version>2.9</jline.version>
+     <jline.version>2.13</jline.version>
 -    <lucene.version>5.1.0_1</lucene.version>
 -    <lucene.osgi-version>5.1.0.1</lucene.osgi-version>
 +    <lucene.version>4.7.0_1</lucene.version>
--- a/distribution/packaging/fedora/thermostat.spec	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/packaging/fedora/thermostat.spec	Tue Feb 09 12:39:50 2016 -0500
@@ -100,6 +100,9 @@
 
 %endif
 
+%global kxml2_version                 2.3.0
+%global osgi_compendium_maven_version 1.4.0
+
 # apache-commons-collections
 %global collections_bundle_version 3.2.1
 
@@ -298,6 +301,14 @@
 BuildRequires: libtool
 # laf-utils JNI need pkconfig files for gtk2+
 BuildRequires: gtk2-devel
+# maven-scr-plugin is needed for DS annotation processing at build-time
+BuildRequires: %{?scl_prefix}mvn(org.apache.felix:maven-scr-plugin)
+# DS annotation processing at build-time (felix annotations)
+BuildRequires: %{?scl_prefix}mvn(org.apache.felix:org.apache.felix.scr.annotations)
+# felix-scr is the DS runtime we use
+BuildRequires: %{?scl_prefix}mvn(org.apache.felix:org.apache.felix.scr)
+BuildRequires: %{?scl_prefix}mvn(org.osgi:org.osgi.compendium)
+BuildRequires: %{?scl_prefix}mvn(net.sf.kxml:kxml2)
 BuildRequires: %{?scl_prefix_java_common}mvn(org.apache.felix:org.apache.felix.framework)
 BuildRequires: %{?scl_prefix_maven}mvn(org.fusesource:fusesource-pom:pom:)
 BuildRequires: %{?scl_prefix_java_common}mvn(org.apache.commons:commons-cli)
@@ -686,7 +697,9 @@
                  -Dosgi.compendium.osgi-version=4.1.0 \
                  -Djgraphx.osgi.version=%{jgraphx_bundle_version} \
                  -Djetty.javax.servlet.osgi.version=%{javax_servlet_bundle_version} \
-                 -Djavax.servlet.bsn=%{javax_servlet_bsn}
+                 -Djavax.servlet.bsn=%{javax_servlet_bsn} \
+                 -Dkxml2.version=%{kxml2_version} \
+                 -Dosgi.compendium.version=%{osgi_compendium_maven_version}
 
 %{?scl:EOF}
 
@@ -798,6 +811,16 @@
 done
 popd
 
+# For some reason the osgi-compendium symlink gets created with a
+# SYSTEM version, but we expect the real version in bootstrap bundles
+# config in main jar.
+pushd %{buildroot}%{thermostat_home}/libs
+  if [ ! -e org.osgi.compendium-%{osgi_compendium_maven_version}.jar ]; then
+    ln -s org.osgi.compendium*.jar org.osgi.compendium-%{osgi_compendium_maven_version}.jar
+  fi
+popd
+
+
 # Add unversioned symlink to netty for command channel script
 ln -s ./netty-%{netty_bundle_version}.jar \
     %{buildroot}%{thermostat_home}/libs/netty.jar
@@ -1032,6 +1055,7 @@
 %{_datadir}/%{pkg_name}/etc
 %{_datadir}/%{pkg_name}/bin
 %{_datadir}/%{pkg_name}/libs
+%{_datadir}/%{pkg_name}/plugins/local
 %{_datadir}/%{pkg_name}/plugins/host-cpu
 %{_datadir}/%{pkg_name}/plugins/host-memory
 %{_datadir}/%{pkg_name}/plugins/host-overview
@@ -1042,7 +1066,10 @@
 %{_datadir}/%{pkg_name}/plugins/thread
 %{_datadir}/%{pkg_name}/plugins/validate
 %{_datadir}/%{pkg_name}/plugins/setup
+%{_datadir}/%{pkg_name}/plugins/platform
+%{_datadir}/%{pkg_name}/plugins/platform-swing
 %{_datadir}/%{pkg_name}/plugins/vm-classstat
+%{_datadir}/%{pkg_name}/plugins/vm-compiler
 %{_datadir}/%{pkg_name}/plugins/vm-cpu
 %{_datadir}/%{pkg_name}/plugins/vm-gc
 %{_datadir}/%{pkg_name}/plugins/vm-heap-analysis
--- a/distribution/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -42,7 +42,7 @@
   <parent>
     <groupId>com.redhat.thermostat</groupId>
     <artifactId>thermostat</artifactId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
 
   <artifactId>thermostat-distribution</artifactId>
@@ -195,6 +195,7 @@
                   <filtering>true</filtering>
                   <includes>
                     <include>shell-command/shell-prompt.conf</include>
+                    <include>platform/*.json</include>
                   </includes>
                 </resource>
                 <resource>
@@ -283,6 +284,8 @@
                       todir="${project.build.directory}/image/libs/native" />
                 <copy file="${main.basedir}/laf-utils/target/libGTKThemeUtils.so"
                       todir="${project.build.directory}/image/libs/native" />
+                <copy file="${main.basedir}/platform/swing/core/target/generated-sources/annotations/template.json"
+                      todir="${project.build.directory}/image/etc/plugins.d/platform" />
               </target>
             </configuration>
             <goals>
@@ -479,6 +482,12 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-local-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-host-overview-distribution</artifactId>
       <version>${project.version}</version>
       <type>zip</type>
@@ -521,6 +530,12 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-compiler-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-vm-cpu-distribution</artifactId>
       <version>${project.version}</version>
       <type>zip</type>
@@ -585,7 +600,18 @@
       <version>${project.version}</version>
       <type>zip</type>
     </dependency>
-
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-platform-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-platform-swing-distribution</artifactId>
+      <version>${project.version}</version>
+      <type>zip</type>
+    </dependency>
     <!-- list-categories command -->
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
--- a/distribution/scripts/thermostat-agent-proxy	Mon Feb 08 18:37:19 2016 -0500
+++ b/distribution/scripts/thermostat-agent-proxy	Tue Feb 09 12:39:50 2016 -0500
@@ -38,6 +38,7 @@
 #
 if [ "$#" -lt 2 ]; then
   echo "usage: $0 <pidOfTargetJvm> <userNameOfJvmOwner>" >&2
+  exit 1
 fi
 
 # Source thermostat-common from same directory as this script
--- a/host-cpu/agent/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/host-cpu/agent/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <artifactId>thermostat-host-cpu</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   <artifactId>thermostat-host-cpu-agent</artifactId>
   <packaging>bundle</packaging>
--- a/host-cpu/client-core/pom.xml	Mon Feb 08 18:37:19 2016 -0500
+++ b/host-cpu/client-core/pom.xml	Tue Feb 09 12:39:50 2016 -0500
@@ -41,7 +41,7 @@
   <parent>
     <artifactId>thermostat-host-cpu</artifactId>
     <groupId>com.redhat.thermostat</groupId>
-    <version>1.99.6-SNAPSHOT</version>
+    <version>1.99.8-SNAPSHOT</version>
   </parent>
   <artifactId>thermostat-host-cpu-client-core</artifactId>
   <packaging>bundle</packaging>
--- a/host-cpu/client-core/src/main/java/com/redhat/thermostat/host/cpu/client/core/HostCpuView.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/host-cpu/client-core/src/main/java/com/redhat/thermostat/host/cpu/client/core/HostCpuView.java	Tue Feb 09 12:39:50 2016 -0500
@@ -38,9 +38,9 @@
 
 import java.util.List;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.Duration;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
--- a/host-cpu/client-core/src/main/resources/com/redhat/thermostat/host/cpu/client/locale/strings.properties	Mon Feb 08 18:37:19 2016 -0500
+++ b/host-cpu/client-core/src/main/resources/com/redhat/thermostat/host/cpu/client/locale/strings.properties	Tue Feb 09 12:39:50 2016 -0500
@@ -4,6 +4,6 @@
 HOST_INFO_CPU_MODEL = Processor Model
 
 HOST_CPU_SECTION_OVERVIEW = Processor
-HOST_CPU_ID = Cpu {0}
+HOST_CPU_ID = CPU {0}
 HOST_CPU_USAGE_CHART_TIME_LABEL = Time
-HOST_CPU_USAGE_CHART_VALUE_LABEL = Cpu Usage (%)
\ No newline at end of file
+HOST_CPU_USAGE_CHART_VALUE_LABEL = CPU Usage (%)
\ No newline at end of file
--- a/host-cpu/client-core/src/test/java/com/redhat/thermostat/host/cpu/client/core/internal/HostCpuControllerTest.java	Mon Feb 08 18:37:19 2016 -0500
+++ b/host-cpu/client-core/src/test/java/com/redhat/thermostat/host/cpu/client/core/internal/HostCpuControllerTest.java	Tue Feb 09 12:39:50 2016 -0500
@@ -55,10 +55,10 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.Applica