]> source.dussan.org Git - vaadin-framework.git/commitdiff
Catch and log exceptions in session lifecycle listeners (#12915)
authorHenri Sara <hesara@vaadin.com>
Wed, 27 Nov 2013 13:30:36 +0000 (15:30 +0200)
committerVaadin Code Review <review@vaadin.com>
Wed, 27 Nov 2013 13:35:27 +0000 (13:35 +0000)
Change-Id: Ie8638f010d74c569c5ff56e91c95e23a5cb92c9b

server/src/com/vaadin/event/EventRouter.java
server/src/com/vaadin/server/VaadinService.java
server/tests/src/com/vaadin/tests/event/EventRouterTest.java [new file with mode: 0644]

index 73bfa33881d1e7b96c8ecf6b1709b969368791e0..fdc543143bcf5fc622030ed95d37b6e1431a7b68 100644 (file)
@@ -23,6 +23,10 @@ import java.util.EventObject;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.server.ErrorEvent;
+import com.vaadin.server.ErrorHandler;
 
 /**
  * <code>EventRouter</code> class implementing the inheritable event listening
@@ -154,6 +158,25 @@ public class EventRouter implements MethodEventSource {
      *            the Event to be sent to all listeners.
      */
     public void fireEvent(EventObject event) {
+        fireEvent(event, null);
+    }
+
+    /**
+     * Sends an event to all registered listeners. The listeners will decide if
+     * the activation method should be called or not.
+     * <p>
+     * If an error handler is set, the processing of other listeners will
+     * continue after the error handler method call unless the error handler
+     * itself throws an exception.
+     * 
+     * @param event
+     *            the Event to be sent to all listeners.
+     * @param errorHandler
+     *            error handler to use to handle any exceptions thrown by
+     *            listeners or null to let the exception propagate to the
+     *            caller, preventing further listener calls
+     */
+    public void fireEvent(EventObject event, ErrorHandler errorHandler) {
         // It is not necessary to send any events if there are no listeners
         if (listenerList != null) {
 
@@ -164,7 +187,16 @@ public class EventRouter implements MethodEventSource {
             // will filter out unwanted events.
             final Object[] listeners = listenerList.toArray();
             for (int i = 0; i < listeners.length; i++) {
-                ((ListenerMethod) listeners[i]).receiveEvent(event);
+                ListenerMethod listenerMethod = (ListenerMethod) listeners[i];
+                if (null != errorHandler) {
+                    try {
+                        listenerMethod.receiveEvent(event);
+                    } catch (Exception e) {
+                        errorHandler.error(new ErrorEvent(e));
+                    }
+                } else {
+                    listenerMethod.receiveEvent(event);
+                }
             }
 
         }
@@ -208,4 +240,9 @@ public class EventRouter implements MethodEventSource {
         }
         return listeners;
     }
+
+    private Logger getLogger() {
+        return Logger.getLogger(EventRouter.class.getName());
+    }
+
 }
index 44ceaaaf87848240ea838dadcaa865c489ab8786..216adce3c820f5e630df83f50fcba125f27eb33a 100644 (file)
@@ -414,6 +414,9 @@ public abstract class VaadinService implements Serializable {
     /**
      * Adds a listener that gets notified when a Vaadin service session that has
      * been initialized for this service is destroyed.
+     * <p>
+     * The session being destroyed is locked and its UIs have been removed when
+     * the listeners are called.
      * 
      * @see #addSessionInitListener(SessionInitListener)
      * 
@@ -455,8 +458,11 @@ public abstract class VaadinService implements Serializable {
                         }
                     });
                 }
+                // for now, use the session error handler; in the future, could
+                // have an API for using some other handler for session init and
+                // destroy listeners
                 eventRouter.fireEvent(new SessionDestroyEvent(
-                        VaadinService.this, session));
+                        VaadinService.this, session), session.getErrorHandler());
             }
         });
     }
@@ -770,7 +776,12 @@ public abstract class VaadinService implements Serializable {
 
     private void onVaadinSessionStarted(VaadinRequest request,
             VaadinSession session) throws ServiceException {
-        eventRouter.fireEvent(new SessionInitEvent(this, session, request));
+        // for now, use the session error handler; in the future, could have an
+        // API for using some other handler for session init and destroy
+        // listeners
+
+        eventRouter.fireEvent(new SessionInitEvent(this, session, request),
+                session.getErrorHandler());
 
         ServletPortletHelper.checkUiProviders(session, this);
     }
diff --git a/server/tests/src/com/vaadin/tests/event/EventRouterTest.java b/server/tests/src/com/vaadin/tests/event/EventRouterTest.java
new file mode 100644 (file)
index 0000000..dbbeaf7
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.event;
+
+import java.lang.reflect.Method;
+
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.event.EventRouter;
+import com.vaadin.server.ErrorEvent;
+import com.vaadin.server.ErrorHandler;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Component.Listener;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Test EventRouter and related error handling.
+ */
+public class EventRouterTest {
+
+    private static final Method COMPONENT_EVENT_METHOD = ReflectTools
+            .findMethod(Component.Listener.class, "componentEvent",
+                    Component.Event.class);
+
+    private EventRouter router;
+    private Component component;
+    private ErrorHandler errorHandler;
+    private Listener listener;
+
+    @Before
+    public void createMocks() {
+        router = new EventRouter();
+        component = EasyMock.createNiceMock(Component.class);
+        errorHandler = EasyMock.createMock(ErrorHandler.class);
+        listener = EasyMock.createMock(Component.Listener.class);
+        router.addListener(Component.Event.class, listener,
+                COMPONENT_EVENT_METHOD);
+    }
+
+    @Test
+    public void fireEvent_noException_eventReceived() {
+        listener.componentEvent(EasyMock.<Component.Event> anyObject());
+
+        EasyMock.replay(component, listener, errorHandler);
+        router.fireEvent(new Component.Event(component), errorHandler);
+        EasyMock.verify(listener, errorHandler);
+    }
+
+    @Test
+    public void fireEvent_exceptionFromListenerAndNoHandler_exceptionPropagated() {
+        listener.componentEvent(EasyMock.<Component.Event> anyObject());
+        EasyMock.expectLastCall().andThrow(
+                new RuntimeException("listener failed"));
+
+        EasyMock.replay(component, listener);
+        try {
+            router.fireEvent(new Component.Event(component));
+            Assert.fail("Did not receive expected exception from listener");
+        } catch (RuntimeException e) {
+            // e is a ListenerMethod@MethodException
+            Assert.assertEquals("listener failed", e.getCause().getMessage());
+        }
+        EasyMock.verify(listener);
+    }
+
+    @Test
+    public void fireEvent_exceptionFromListener_errorHandlerCalled() {
+        listener.componentEvent(EasyMock.<Component.Event> anyObject());
+        EasyMock.expectLastCall().andThrow(
+                new RuntimeException("listener failed"));
+        errorHandler.error(EasyMock.<ErrorEvent> anyObject());
+
+        EasyMock.replay(component, listener, errorHandler);
+        router.fireEvent(new Component.Event(component), errorHandler);
+        EasyMock.verify(listener, errorHandler);
+    }
+
+    @Test
+    public void fireEvent_multipleListenersAndException_errorHandlerCalled() {
+        Listener listener2 = EasyMock.createMock(Component.Listener.class);
+        router.addListener(Component.Event.class, listener2,
+                COMPONENT_EVENT_METHOD);
+
+        listener.componentEvent(EasyMock.<Component.Event> anyObject());
+        EasyMock.expectLastCall().andThrow(
+                new RuntimeException("listener failed"));
+        errorHandler.error(EasyMock.<ErrorEvent> anyObject());
+        // second listener should be called despite an error in the first
+        listener2.componentEvent(EasyMock.<Component.Event> anyObject());
+
+        EasyMock.replay(component, listener, listener2, errorHandler);
+        router.fireEvent(new Component.Event(component), errorHandler);
+        EasyMock.verify(listener, listener2, errorHandler);
+    }
+}