changeset 1198:9cb09d6aeddb

Support web storage in JMX plugin This commit fixes the JMX plugin to work with web storage. Aside from needing a command channel action and associated role, there were a few other issues this commit addresses. Most importantly, I noticed the GUI would hang when trying to enable JMX notifications without proper permissions after the third attempt. This seems to be due to another issue where the web service stops responding to the client's requests. I traced the hang down to waiting on the network for AgentInfoDAO.getAgentInformation. I've fixed the GUI hang by making the toggle notifications action asynchronous, running in an executor provided by ApplicationService. Another fix in this commit is provided feedback to the user when authentication fails when trying to toggle JMX notifications. This is done using a dialog box similar to the thread plugin. One final small fix is to add the jmx-common bundle to the webservice command in thermostat-plugin.xml. Without this, the webservice cannot find the JMX storage entities it needs. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-July/007712.html
author Elliott Baron <ebaron@redhat.com>
date Thu, 01 Aug 2013 14:05:00 -0400
parents 74c7131a0bb9
children 1e59f46b6efa
files vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/LocaleResources.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/Activator.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImpl.java vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequest.java vm-jmx/client-core/src/main/resources/com/redhat/thermostat/vm/jmx/client/core/strings.properties vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/ActivatorTest.java vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImplTest.java vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequestTest.java vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java vm-jmx/distribution/thermostat-plugin.xml
diffstat 13 files changed, 390 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/JmxNotificationsView.java	Thu Aug 01 14:05:00 2013 -0400
@@ -39,6 +39,7 @@
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.vm.jmx.common.JmxNotification;
 
 public abstract class JmxNotificationsView extends BasicView implements UIComponent {
@@ -53,5 +54,6 @@
     public abstract void setNotificationsEnabled(boolean enabled);
     public abstract void clearNotifications();
     public abstract void addNotification(JmxNotification data);
+    public abstract void displayWarning(LocalizedString warning);
 
 }
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/LocaleResources.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/LocaleResources.java	Thu Aug 01 14:05:00 2013 -0400
@@ -49,6 +49,9 @@
     NOTIFICATIONS_ENABLE_DESCRIPTION,
     NOTIFICATIONS_DISABLE,
     NOTIFICATIONS_DISABLE_DESCRIPTION,
+    
+    NOTIFICATIONS_CANNOT_ENABLE,
+    NOTIFICATIONS_CANNOT_DISABLE,
     ;
 
     static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.jmx.client.core.strings";
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/Activator.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/Activator.java	Thu Aug 01 14:05:00 2013 -0400
@@ -65,6 +65,7 @@
     public void start(final BundleContext context) throws Exception {
 
         final Class<?>[] deps = new Class<?>[] {
+                ApplicationService.class,
                 AgentInfoDAO.class,
                 JmxNotificationDAO.class,
                 ApplicationService.class,
@@ -75,13 +76,14 @@
         depsTracker = new MultipleServiceTracker(context, deps, new Action() {
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
+                ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
                 AgentInfoDAO agentDao = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
                 JmxNotificationDAO notificationDao = (JmxNotificationDAO) services.get(JmxNotificationDAO.class.getName());
                 JmxNotificationsViewProvider viewProvider = (JmxNotificationsViewProvider) services.get(JmxNotificationsViewProvider.class.getName());
                 TimerFactory tf = ((ApplicationService) services.get(ApplicationService.class.getName())).getTimerFactory();
                 RequestQueue queue = (RequestQueue) services.get(RequestQueue.class.getName());
 
-                JmxNotificationsViewServiceImpl notificationsView = new JmxNotificationsViewServiceImpl(agentDao, notificationDao, queue, tf, viewProvider);
+                JmxNotificationsViewServiceImpl notificationsView = new JmxNotificationsViewServiceImpl(appSvc, agentDao, notificationDao, queue, tf, viewProvider);
 
                 Dictionary props = new Hashtable();
                 props.put(Constants.GENERIC_SERVICE_CLASSNAME, VmRef.class.getName());
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewController.java	Thu Aug 01 14:05:00 2013 -0400
@@ -47,6 +47,7 @@
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.shared.locale.LocalizedString;
@@ -66,21 +67,57 @@
     private final JmxNotificationsView view;
     private final Timer timer;
     private final JmxNotificationDAO dao;
-    private final AgentInfoDAO agentDAO;
     private final VmRef vm;
     private final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private final JmxToggleNotificationRequest toggleReq;
 
     private final AtomicBoolean notificationsEnabled = new AtomicBoolean(false);
-
-    public JmxNotificationsViewController(AgentInfoDAO agent, JmxNotificationDAO notification,
-            TimerFactory timerFactory, final RequestQueue queue,
-            JmxNotificationsViewProvider viewProvider,
-            VmRef vmId) {
+    
+    public JmxNotificationsViewController(ApplicationService appSvc,
+            AgentInfoDAO agent, JmxNotificationDAO notification,
+            TimerFactory timerFactory, RequestQueue queue,
+            JmxNotificationsViewProvider viewProvider, VmRef vmId) {
+        this(appSvc, agent, notification, timerFactory, queue, viewProvider, vmId,
+                new JmxToggleNotificationRequestFactory());
+    }
+    
+    JmxNotificationsViewController(final ApplicationService appSvc,
+            AgentInfoDAO agent, JmxNotificationDAO notification,
+            TimerFactory timerFactory, RequestQueue queue,
+            JmxNotificationsViewProvider viewProvider, VmRef vmId,
+            JmxToggleNotificationRequestFactory reqFactory) {
         this.dao = notification;
-        this.agentDAO = agent;
         this.view = viewProvider.createView();
         this.timer = timerFactory.createTimer();
         this.vm = vmId;
+        
+        // Callbacks for toggle notifications
+        final Runnable successAction = new Runnable() {
+
+            @Override
+            public void run() {
+                notificationsEnabled.set(!notificationsEnabled.get());
+                view.setNotificationsEnabled(notificationsEnabled.get());
+            }
+        };
+
+        final Runnable failureAction = new Runnable() {
+
+            @Override
+            public void run() {
+                LocalizedString warning;
+                if (notificationsEnabled.get()) {
+                    warning = t.localize(LocaleResources.NOTIFICATIONS_CANNOT_DISABLE);
+                }
+                else {
+                    warning = t.localize(LocaleResources.NOTIFICATIONS_CANNOT_ENABLE);
+                }
+                view.displayWarning(warning);
+                view.setNotificationsEnabled(notificationsEnabled.get());
+            }
+        };
+        
+        this.toggleReq = reqFactory.createRequest(queue, agent, successAction, failureAction);
 
         initializeTimer();
 
@@ -103,11 +140,13 @@
             @Override
             public void actionPerformed(ActionEvent<NotificationAction> actionEvent) {
                 if (actionEvent.getActionId() == NotificationAction.TOGGLE_NOTIFICATIONS) {
-                    notificationsEnabled.set(!notificationsEnabled.get());
-
-                    new JmxToggleNotificationRequest(queue).sendEnableNotificationsRequestToAgent(vm, agentDAO, notificationsEnabled.get(), null);
-
-                    view.setNotificationsEnabled(notificationsEnabled.get());
+                    // This can block on network, do outside EDT/UI thread
+                    appSvc.getApplicationExecutor().execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            toggleReq.sendEnableNotificationsRequestToAgent(vm, !notificationsEnabled.get());
+                        }
+                    });
                 }
             }
         });
@@ -156,5 +195,14 @@
     public LocalizedString getLocalizedName() {
         return t.localize(LocaleResources.NOTIFICATIONS_TITLE);
     }
+    
+    static class JmxToggleNotificationRequestFactory {
+        
+        JmxToggleNotificationRequest createRequest(RequestQueue queue, AgentInfoDAO agentDAO, 
+                Runnable successAction, Runnable failureAction) {
+            return new JmxToggleNotificationRequest(queue, agentDAO, successAction, failureAction);
+        }
+        
+    }
 
 }
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImpl.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImpl.java	Thu Aug 01 14:05:00 2013 -0400
@@ -40,6 +40,7 @@
 import com.redhat.thermostat.client.core.Filter;
 import com.redhat.thermostat.client.core.NameMatchingRefFilter;
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.storage.core.VmRef;
@@ -52,14 +53,18 @@
 
     private final Filter<VmRef> FILTER = new NameMatchingRefFilter<>();
 
+    private final ApplicationService appSvc;
     private final JmxNotificationsViewProvider viewProvider;
     private final JmxNotificationDAO notificationDao;
     private final AgentInfoDAO agentDao;
     private final RequestQueue requestQueue;
     private final TimerFactory timerFactory;
 
-    public JmxNotificationsViewServiceImpl(AgentInfoDAO agentDao, JmxNotificationDAO notificationDao,
-            RequestQueue requestQueue, TimerFactory timerFactory, JmxNotificationsViewProvider viewProvider) {
+    public JmxNotificationsViewServiceImpl(ApplicationService appSvc,
+            AgentInfoDAO agentDao, JmxNotificationDAO notificationDao,
+            RequestQueue requestQueue, TimerFactory timerFactory,
+            JmxNotificationsViewProvider viewProvider) {
+        this.appSvc = appSvc;
         this.agentDao = agentDao;
         this.notificationDao = notificationDao;
         this.timerFactory = timerFactory;
@@ -74,7 +79,8 @@
 
     @Override
     public InformationServiceController<VmRef> getInformationServiceController(VmRef ref) {
-        return new JmxNotificationsViewController(agentDao, notificationDao, timerFactory, requestQueue, viewProvider, ref);
+        return new JmxNotificationsViewController(appSvc, agentDao, notificationDao, timerFactory,
+                requestQueue, viewProvider, ref);
     }
 
     @Override
--- a/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequest.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequest.java	Thu Aug 01 14:05:00 2013 -0400
@@ -40,43 +40,89 @@
 
 import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Request.RequestType;
 import com.redhat.thermostat.common.command.RequestResponseListener;
-import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.vm.jmx.common.JmxCommand;
 
 public class JmxToggleNotificationRequest {
+    
+    static final String CMD_CHANNEL_ACTION_NAME = "jmx-toggle-notifications";
 
     private RequestQueue queue;
+    private AgentInfoDAO agentDAO;
+    private Runnable successAction;
+    private Runnable failureAction;
+    private JmxToggleResponseListenerFactory factory;
 
-    public JmxToggleNotificationRequest(RequestQueue queue) {
+    public JmxToggleNotificationRequest(RequestQueue queue, AgentInfoDAO agentDAO,
+            Runnable successAction, Runnable failureAction) {
+        this(queue, agentDAO, successAction, failureAction, new JmxToggleResponseListenerFactory());
+    }
+    
+    JmxToggleNotificationRequest(RequestQueue queue, AgentInfoDAO agentDAO, Runnable successAction, 
+            Runnable failureAction, JmxToggleResponseListenerFactory factory) {
         this.queue = queue;
+        this.agentDAO = agentDAO;
+        this.successAction = successAction;
+        this.failureAction = failureAction;
+        this.factory = factory;
     }
 
-    public void sendEnableNotificationsRequestToAgent(VmRef vm, AgentInfoDAO agentDAO, boolean enable, RequestResponseListener responseListener) {
-
+    public void sendEnableNotificationsRequestToAgent(VmRef vm, boolean enable) {
         HostRef targetHostRef = vm.getHostRef();
 
         String address = agentDAO.getAgentInformation(targetHostRef).getConfigListenAddress();
         String[] host = address.split(":");
 
         InetSocketAddress target = new InetSocketAddress(host[0], Integer.parseInt(host[1]));
-        Request gcRequest = createRequest(target);
+        Request req = new Request(RequestType.RESPONSE_EXPECTED, target);
+
+        req.setReceiver(JmxCommand.RECEIVER);
 
-        gcRequest.setReceiver(JmxCommand.RECEIVER);
+        req.setParameter(Request.ACTION, CMD_CHANNEL_ACTION_NAME);
+        req.setParameter(JmxCommand.class.getName(), enable ? JmxCommand.ENABLE_JMX_NOTIFICATIONS.name() : JmxCommand.DISABLE_JMX_NOTIFICATIONS.name());
+        req.setParameter(JmxCommand.VM_PID, String.valueOf(vm.getPid()));
 
-        gcRequest.setParameter(JmxCommand.class.getName(), enable ? JmxCommand.ENABLE_JMX_NOTIFICATIONS.name() : JmxCommand.DISABLE_JMX_NOTIFICATIONS.name());
-        gcRequest.setParameter(JmxCommand.VM_PID, String.valueOf(vm.getPid()));
+        JmxToggleResponseListener listener = factory.createListener(successAction, failureAction);
+        req.addListener(listener);
 
-        gcRequest.addListener(responseListener);
-
-        queue.putRequest(gcRequest);
+        queue.putRequest(req);
     }
+    
+    static class JmxToggleResponseListener implements RequestResponseListener {
+        
+        private Runnable successAction;
+        private Runnable failureAction;
+        
+        public JmxToggleResponseListener(Runnable successAction, Runnable failureAction) {
+            this.successAction = successAction;
+            this.failureAction = failureAction;
+        }
 
-    // for testing
-    Request createRequest(InetSocketAddress target) {
-        return new Request(RequestType.RESPONSE_EXPECTED, target);
+        @Override
+        public void fireComplete(Request request, Response response) {
+            switch (response.getType()) {
+            case OK:
+                successAction.run();
+                break;
+            default:
+                failureAction.run();
+                break;
+            }
+        }
+        
+    }
+    
+    static class JmxToggleResponseListenerFactory {
+        
+        JmxToggleResponseListener createListener(Runnable successAction, 
+                Runnable failureAction) {
+            return new JmxToggleResponseListener(successAction, failureAction);
+        }
+        
     }
 }
--- a/vm-jmx/client-core/src/main/resources/com/redhat/thermostat/vm/jmx/client/core/strings.properties	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/main/resources/com/redhat/thermostat/vm/jmx/client/core/strings.properties	Thu Aug 01 14:05:00 2013 -0400
@@ -7,3 +7,6 @@
 NOTIFICATIONS_ENABLE_DESCRIPTION = Start Monitoring Jmx Notifications
 NOTIFICATIONS_DISABLE = Stop Monitoring
 NOTIFICATIONS_DISABLE_DESCRIPTION = Stop Monitoring Jmx Notifications
+
+NOTIFICATIONS_CANNOT_ENABLE = Could not start monitoring JMX Notifications
+NOTIFICATIONS_CANNOT_DISABLE = Could not stop monitoring JMX Notifications
--- a/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/ActivatorTest.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/ActivatorTest.java	Thu Aug 01 14:05:00 2013 -0400
@@ -56,6 +56,7 @@
     public void testActivatorRegistersService() throws Exception {
         StubBundleContext bundleContext = new StubBundleContext();
 
+        bundleContext.registerService(ApplicationService.class, mock(ApplicationService.class), null);
         bundleContext.registerService(AgentInfoDAO.class, mock(AgentInfoDAO.class), null);
         bundleContext.registerService(JmxNotificationDAO.class, mock(JmxNotificationDAO.class), null);
         bundleContext.registerService(ApplicationService.class, mock(ApplicationService.class), null);
--- a/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewControllerTest.java	Thu Aug 01 14:05:00 2013 -0400
@@ -37,40 +37,51 @@
 package com.redhat.thermostat.vm.jmx.client.core.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.net.InetSocketAddress;
 import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.client.core.views.BasicView.Action;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.common.TimerFactory;
-import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
-import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.vm.jmx.client.core.JmxNotificationsView;
 import com.redhat.thermostat.vm.jmx.client.core.JmxNotificationsView.NotificationAction;
 import com.redhat.thermostat.vm.jmx.client.core.JmxNotificationsViewProvider;
+import com.redhat.thermostat.vm.jmx.client.core.LocaleResources;
+import com.redhat.thermostat.vm.jmx.client.core.internal.JmxNotificationsViewController.JmxToggleNotificationRequestFactory;
 import com.redhat.thermostat.vm.jmx.common.JmxNotification;
 import com.redhat.thermostat.vm.jmx.common.JmxNotificationDAO;
 import com.redhat.thermostat.vm.jmx.common.JmxNotificationStatus;
 
 public class JmxNotificationsViewControllerTest {
 
-    private AgentInfoDAO agentDao;
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    
     private JmxNotificationDAO notificationDao;
     private JmxNotificationsView view;
     private JmxNotificationsViewProvider viewProvider;
@@ -80,10 +91,28 @@
     private RequestQueue queue;
     private JmxNotificationsViewController controller;
     private HostRef host;
+    private JmxToggleNotificationRequest toggleReq;
+
+    private Runnable successAction;
+
+    private Runnable failureAction;
 
     @Before
     public void setUp() {
-        agentDao = mock(AgentInfoDAO.class);
+        ApplicationService appSvc = mock(ApplicationService.class);
+        ExecutorService execSvc = mock(ExecutorService.class);
+        when(appSvc.getApplicationExecutor()).thenReturn(execSvc);
+        // Run task immediately
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Runnable runnable = (Runnable) invocation.getArguments()[0];
+                runnable.run();
+                return null;
+            }
+        }).when(execSvc).execute(any(Runnable.class));
+        
+        AgentInfoDAO agentDao = mock(AgentInfoDAO.class);
         notificationDao = mock(JmxNotificationDAO.class);
         queue = mock(RequestQueue.class);
         view = mock(JmxNotificationsView.class);
@@ -96,8 +125,20 @@
         host = mock(HostRef.class);
         vm = mock(VmRef.class);
         when(vm.getHostRef()).thenReturn(host);
+        
+        JmxToggleNotificationRequestFactory reqFactory = mock(JmxToggleNotificationRequestFactory.class);
+        toggleReq = mock(JmxToggleNotificationRequest.class);
+        when(reqFactory.createRequest(eq(queue), eq(agentDao), any(Runnable.class), 
+                any(Runnable.class))).thenReturn(toggleReq);
 
-        controller = new JmxNotificationsViewController(agentDao, notificationDao, timerFactory, queue, viewProvider, vm);
+        controller = new JmxNotificationsViewController(appSvc, agentDao, notificationDao, timerFactory, 
+                queue, viewProvider, vm, reqFactory);
+        ArgumentCaptor<Runnable> successCaptor = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Runnable> failureCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(reqFactory).createRequest(eq(queue), eq(agentDao), successCaptor.capture(), failureCaptor.capture());
+        
+        successAction = successCaptor.getValue();
+        failureAction = failureCaptor.getValue();
     }
 
     @Test
@@ -169,17 +210,91 @@
         ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
 
         verify(view).addNotificationActionListener(listenerCaptor.capture());
+        answerSuccess(true);
 
-        AgentInformation agentInfo = mock(AgentInformation.class);
-        when(agentInfo.getConfigListenAddress()).thenReturn("example.com:0");
-        when(agentDao.getAgentInformation(host)).thenReturn(agentInfo);
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
+
+        verify(toggleReq).sendEnableNotificationsRequestToAgent(eq(vm), eq(true));
+        verify(view).setNotificationsEnabled(true);
+    }
+    
+    @Test
+    public void enableNotificationsFails() {
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+
+        verify(view).addNotificationActionListener(listenerCaptor.capture());
+        answerFailure(true);
 
         listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
 
-        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
-        verify(queue).putRequest(requestCaptor.capture());
+        verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, true);
+        verify(view, never()).setNotificationsEnabled(true);
+        verify(view).setNotificationsEnabled(false);
+        
+        ArgumentCaptor<LocalizedString> warningCaptor = ArgumentCaptor.forClass(LocalizedString.class);
+        verify(view).displayWarning(warningCaptor.capture());
+        assertEquals(translator.localize(LocaleResources.NOTIFICATIONS_CANNOT_ENABLE).getContents(), 
+                warningCaptor.getValue().getContents());
+    }
+    
+    @Test
+    public void disableNotificationsWhenViewFiresEvent() {
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+
+        verify(view).addNotificationActionListener(listenerCaptor.capture());
+        answerSuccess(true);
+        answerSuccess(false);
+
+        // Enable, then disable
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
+
+        verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, false);
+        verify(view).setNotificationsEnabled(false);
+    }
+
+    private void answerSuccess(boolean enable) {
+        doAnswer(new Answer<Void>() {
 
-        Request req = requestCaptor.getValue();
-        assertEquals(new InetSocketAddress("example.com", 0), req.getTarget());
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                successAction.run();
+                return null;
+            }
+        }).when(toggleReq).sendEnableNotificationsRequestToAgent(vm, enable);
+    }
+
+    private void answerFailure(boolean enable) {
+        doAnswer(new Answer<Void>() {
+
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                failureAction.run();
+                return null;
+            }
+        }).when(toggleReq).sendEnableNotificationsRequestToAgent(vm, enable);
     }
+
+    @Test
+    public void disableNotificationsFails() {
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+
+        verify(view).addNotificationActionListener(listenerCaptor.capture());
+        answerSuccess(true);
+        answerFailure(false);
+        
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, NotificationAction.TOGGLE_NOTIFICATIONS));
+
+        verify(toggleReq).sendEnableNotificationsRequestToAgent(vm, false);
+        verify(view, never()).setNotificationsEnabled(false);
+        verify(view, times(2)).setNotificationsEnabled(true);
+        
+        ArgumentCaptor<LocalizedString> warningCaptor = ArgumentCaptor.forClass(LocalizedString.class);
+        verify(view).displayWarning(warningCaptor.capture());
+        assertEquals(translator.localize(LocaleResources.NOTIFICATIONS_CANNOT_DISABLE).getContents(), 
+                warningCaptor.getValue().getContents());
+    }
+
+    
 }
--- a/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImplTest.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxNotificationsViewServiceImplTest.java	Thu Aug 01 14:05:00 2013 -0400
@@ -45,6 +45,7 @@
 import org.junit.Test;
 
 import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.Ordered;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.TimerFactory;
@@ -67,6 +68,7 @@
 
     @Before
     public void setUp() {
+        ApplicationService appSvc = mock(ApplicationService.class);
         agentDao = mock(AgentInfoDAO.class);
         notificationDao = mock(JmxNotificationDAO.class);
         timer = mock(Timer.class);
@@ -79,7 +81,8 @@
         viewProvider = mock(JmxNotificationsViewProvider.class);
         when(viewProvider.createView()).thenReturn(view);
 
-        service = new JmxNotificationsViewServiceImpl(agentDao, notificationDao, queue, timerFactory, viewProvider);
+        service = new JmxNotificationsViewServiceImpl(appSvc, agentDao, notificationDao, 
+                queue, timerFactory, viewProvider);
     }
 
     @Test
--- a/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequestTest.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-core/src/test/java/com/redhat/thermostat/vm/jmx/client/core/internal/JmxToggleNotificationRequestTest.java	Thu Aug 01 14:05:00 2013 -0400
@@ -37,23 +37,33 @@
 package com.redhat.thermostat.vm.jmx.client.core.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.net.InetSocketAddress;
+import java.util.Collection;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.vm.jmx.client.core.internal.JmxToggleNotificationRequest.JmxToggleResponseListener;
+import com.redhat.thermostat.vm.jmx.client.core.internal.JmxToggleNotificationRequest.JmxToggleResponseListenerFactory;
 import com.redhat.thermostat.vm.jmx.common.JmxCommand;
 
 public class JmxToggleNotificationRequestTest {
@@ -66,8 +76,12 @@
     private HostRef host;
     private VmRef vm;
     private AgentInfoDAO agentDAO;
-    private RequestResponseListener responseListener;
     private AgentInformation agentInfo;
+    private JmxToggleResponseListenerFactory factory;
+    private JmxToggleResponseListener listener;
+    private Runnable successAction;
+    private Runnable failureAction;
+    private JmxToggleNotificationRequest toggleReq;
 
     @Before
     public void setUp() {
@@ -83,37 +97,101 @@
         agentInfo = mock(AgentInformation.class);
         when(agentInfo.getConfigListenAddress()).thenReturn(HOST + ":" + PORT);
         when(agentDAO.getAgentInformation(host)).thenReturn(agentInfo);
-
-        responseListener = mock(RequestResponseListener.class);
+        
+        factory = mock(JmxToggleResponseListenerFactory.class);
+        listener = mock(JmxToggleResponseListener.class);
+        successAction = mock(Runnable.class);
+        failureAction = mock(Runnable.class);
+        when(factory.createListener(successAction, failureAction)).thenReturn(listener);
+        
+        toggleReq = new JmxToggleNotificationRequest(queue, agentDAO, successAction, failureAction);
     }
 
     @Test
-    public void testEnableNotificationMessage() {
-        new JmxToggleNotificationRequest(queue).sendEnableNotificationsRequestToAgent(vm, agentDAO, true, responseListener);
+    public void testEnableNotificationsSuccess() {
+        answerSuccess();
+        toggleReq.sendEnableNotificationsRequestToAgent(vm, true);
+
+        verify(queue).putRequest(requestCaptor.capture());
+
+        Request req = requestCaptor.getValue();
+
+        assertEquals(new InetSocketAddress(HOST, PORT), req.getTarget());
+        assertEquals(JmxToggleNotificationRequest.CMD_CHANNEL_ACTION_NAME, req.getParameter(Request.ACTION));
+        assertEquals(JmxCommand.RECEIVER, req.getReceiver());
+        assertEquals(String.valueOf(vm.getPid()), req.getParameter(JmxCommand.VM_PID));
+
+        assertEquals(JmxCommand.ENABLE_JMX_NOTIFICATIONS.name(), req.getParameter(JmxCommand.class.getName()));
+        
+        verify(successAction).run();
+        verify(failureAction, never()).run();
+    }
+    
+    @Test
+    public void testEnableNotificationsFailure() {
+        answerFailure();
+        toggleReq.sendEnableNotificationsRequestToAgent(vm, true);
+
+        verify(successAction, never()).run();
+        verify(failureAction).run();
+    }
+
+    @Test
+    public void testDisableNotificationsSuccess() {
+        answerSuccess();
+        toggleReq.sendEnableNotificationsRequestToAgent(vm, false);
 
         verify(queue).putRequest(requestCaptor.capture());
 
         Request req = requestCaptor.getValue();
 
         assertEquals(new InetSocketAddress(HOST, PORT), req.getTarget());
-        assertEquals(JmxCommand.RECEIVER, req.getReceiver());
-        assertEquals(String.valueOf(vm.getPid()), req.getParameter(JmxCommand.VM_PID));
-
-        assertEquals(JmxCommand.ENABLE_JMX_NOTIFICATIONS.name(), req.getParameter(JmxCommand.class.getName()));
-    }
-
-    @Test
-    public void testDisableNotificationMessage() {
-        new JmxToggleNotificationRequest(queue).sendEnableNotificationsRequestToAgent(vm, agentDAO, false, responseListener);
-
-        verify(queue).putRequest(requestCaptor.capture());
-
-        Request req = requestCaptor.getValue();
-
-        assertEquals(new InetSocketAddress(HOST, PORT), req.getTarget());
+        assertEquals(JmxToggleNotificationRequest.CMD_CHANNEL_ACTION_NAME, req.getParameter(Request.ACTION));
         assertEquals(JmxCommand.RECEIVER, req.getReceiver());
         assertEquals(String.valueOf(vm.getPid()), req.getParameter(JmxCommand.VM_PID));
 
         assertEquals(JmxCommand.DISABLE_JMX_NOTIFICATIONS.name(), req.getParameter(JmxCommand.class.getName()));
+        
+        verify(successAction).run();
+        verify(failureAction, never()).run();
+    }
+    
+    @Test
+    public void testDisableNotificationsFailure() {
+        answerFailure();
+        toggleReq.sendEnableNotificationsRequestToAgent(vm, false);
+
+        verify(successAction, never()).run();
+        verify(failureAction).run();
+    }
+    
+    private void answerSuccess() {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                // Fire complete OK
+                Request req = (Request) invocation.getArguments()[0];
+                Collection<RequestResponseListener> listeners = req.getListeners();
+                assertEquals(1, listeners.size());
+                RequestResponseListener listener = listeners.iterator().next();
+                listener.fireComplete(req, new Response(ResponseType.OK));
+                return null;
+            }
+        }).when(queue).putRequest(any(Request.class));
+    }
+
+    private void answerFailure() {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                // Fire complete ERROR
+                Request req = (Request) invocation.getArguments()[0];
+                Collection<RequestResponseListener> listeners = req.getListeners();
+                assertEquals(1, listeners.size());
+                RequestResponseListener listener = listeners.iterator().next();
+                listener.fireComplete(req, new Response(ResponseType.ERROR));
+                return null;
+            }
+        }).when(queue).putRequest(any(Request.class));
     }
 }
--- a/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Thu Aug 01 14:05:00 2013 -0400
@@ -47,6 +47,7 @@
 import javax.swing.ButtonModel;
 import javax.swing.DefaultListModel;
 import javax.swing.JList;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.SwingUtilities;
@@ -61,6 +62,7 @@
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.vm.jmx.client.core.JmxNotificationsView;
 import com.redhat.thermostat.vm.jmx.client.core.LocaleResources;
@@ -190,4 +192,14 @@
         return visiblePanel;
     }
 
+    @Override
+    public void displayWarning(final LocalizedString warning) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                JOptionPane.showMessageDialog(visiblePanel.getParent(), warning.getContents(), "", JOptionPane.WARNING_MESSAGE);
+            }
+        });
+    }
+
 }
--- a/vm-jmx/distribution/thermostat-plugin.xml	Thu Aug 01 13:40:31 2013 -0400
+++ b/vm-jmx/distribution/thermostat-plugin.xml	Thu Aug 01 14:05:00 2013 -0400
@@ -55,5 +55,12 @@
         <bundle><symbolic-name>com.redhat.thermostat.vm.jmx.agent</symbolic-name><version>${project.version}</version></bundle>
       </bundles>
     </extension>
+    <!-- Requires all storage entity classes to be loaded -->
+    <extension>
+      <name>webservice</name>
+      <bundles>
+        <bundle><symbolic-name>com.redhat.thermostat.vm.jmx.common</symbolic-name><version>${project.version}</version></bundle>
+      </bundles>
+    </extension>
   </extensions>
 </plugin>