Author: dblevins
Date: Sun Mar 23 23:02:46 2014
New Revision: 1580656

URL: http://svn.apache.org/r1580656
Log:
TOMEE-1152 - Failure related @Observes infinite loops protection 

Added:
    
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java
   (with props)
Modified:
    
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/ObserverManager.java
    
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/event/ObserverFailed.java
    
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/ObserverFeaturesTest.java
    
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Util.java

Modified: 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/ObserverManager.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/ObserverManager.java?rev=1580656&r1=1580655&r2=1580656&view=diff
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/ObserverManager.java
 (original)
+++ 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/ObserverManager.java
 Sun Mar 23 23:02:46 2014
@@ -19,6 +19,7 @@ package org.apache.openejb.observer;
 import org.apache.openejb.observer.event.AfterEvent;
 import org.apache.openejb.observer.event.BeforeEvent;
 import org.apache.openejb.observer.event.ObserverAdded;
+import org.apache.openejb.observer.event.ObserverFailed;
 import org.apache.openejb.observer.event.ObserverRemoved;
 
 import java.lang.annotation.Annotation;
@@ -36,8 +37,21 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 public class ObserverManager {
+
+    private final Stack stack = new Stack();
+
+    private static final ThreadLocal<Set<Invocation>> seen = new 
ThreadLocal<Set<Invocation>>() {
+        @Override
+        protected Set<Invocation> initialValue() {
+            return new HashSet<Invocation>();
+        }
+    };
+
+    private static final Logger LOGGER = 
Logger.getLogger(ObserverManager.class.getName());
     private final Set<Observer> observers = new LinkedHashSet<Observer>();
     private final Map<Class, Invocation> methods = new 
ConcurrentHashMap<Class, Invocation>();
 
@@ -79,6 +93,14 @@ public class ObserverManager {
             throw new IllegalArgumentException("event cannot be null");
         }
 
+        try {
+            return doFire(event);
+        } finally {
+            seen.remove();
+        }
+    }
+
+    private <E> E doFire(E event) {
         final Class<?> type = event.getClass();
 
         final Invocation invocation = getInvocation(type);
@@ -154,7 +176,7 @@ public class ObserverManager {
     /**
      * @version $Rev$ $Date$
      */
-    public static class Observer {
+    public class Observer {
 
         private final Map<Class, Invocation> before = new 
ConcurrentHashMap<Class, Invocation>();
         private final Map<Class, Invocation> methods = new 
ConcurrentHashMap<Class, Invocation>();
@@ -355,7 +377,7 @@ public class ObserverManager {
     };
 
 
-    public static class MethodInvocation implements Invocation {
+    public class MethodInvocation implements Invocation {
         private final Method method;
         private final Object observer;
 
@@ -369,11 +391,19 @@ public class ObserverManager {
             try {
                 method.invoke(observer, event);
             } catch (InvocationTargetException e) {
+                if (!seen.get().add(this)) return;
+
                 final Throwable t = e.getTargetException() == null ? e : 
e.getTargetException();
 
-//                if (e.getTargetException() != null) {
-//                    logger.log(Level.WARNING, "Observer method invocation 
failed", t);
-//                }
+                if (!(event instanceof ObserverFailed)) {
+                    doFire(new ObserverFailed(observer, method, event, t));
+                }
+
+                if (t instanceof InvocationTargetException && t.getCause() != 
null) {
+                    LOGGER.log(Level.SEVERE, "error invoking " + observer, 
t.getCause());
+                } else {
+                    LOGGER.log(Level.SEVERE, "error invoking " + observer, t);
+                }
             } catch (IllegalAccessException e) {
                 e.printStackTrace();
             }
@@ -385,7 +415,28 @@ public class ObserverManager {
         }
     }
 
-    private static class AfterInvocation extends MethodInvocation {
+    private static class Stack {
+        private final int[] seen = new int[10];
+        private int i = 0;
+
+        public boolean seen(Invocation invocation) {
+            int code = invocation.hashCode();
+
+            for (int j = 0; j < seen.length; j++) {
+                if (seen[j] == code) return true;
+            }
+
+            seen[i++] = code;
+
+            if (i >= seen.length) {
+                i = 0;
+            }
+
+            return false;
+        }
+    }
+
+    private class AfterInvocation extends MethodInvocation {
 
         private AfterInvocation(Method method, Object observer) {
             super(method, observer);
@@ -402,7 +453,7 @@ public class ObserverManager {
         }
     }
 
-    private static class BeforeInvocation extends MethodInvocation {
+    private class BeforeInvocation extends MethodInvocation {
 
         private BeforeInvocation(Method method, Object observer) {
             super(method, observer);

Modified: 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/event/ObserverFailed.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/event/ObserverFailed.java?rev=1580656&r1=1580655&r2=1580656&view=diff
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/event/ObserverFailed.java
 (original)
+++ 
tomee/tomee/trunk/container/openejb-loader/src/main/java/org/apache/openejb/observer/event/ObserverFailed.java
 Sun Mar 23 23:02:46 2014
@@ -18,6 +18,8 @@ package org.apache.openejb.observer.even
 
 import org.apache.openejb.observer.Event;
 
+import java.lang.reflect.Method;
+
 /**
  * @version $Rev$ $Date$
  */
@@ -26,16 +28,23 @@ public class ObserverFailed {
 
     private final Object observer;
 
+    private final Method method;
+
     private final Object event;
 
     private final Throwable throwable;
 
-    public ObserverFailed(Object observer, Object event, Throwable throwable) {
+    public ObserverFailed(Object observer, Method method, Object event, 
Throwable throwable) {
         this.observer = observer;
         this.event = event;
+        this.method = method;
         this.throwable = throwable;
     }
 
+    public Method getMethod() {
+        return method;
+    }
+
     public Object getObserver() {
         return observer;
     }
@@ -52,6 +61,7 @@ public class ObserverFailed {
     public String toString() {
         return "ObserverFailed{" +
                 "observer=" + observer.getClass().getName() +
+                ", method='" + method.toString() + "'" +
                 ", throwable=" + throwable.getClass().getName() +
                 "} " + event;
     }

Added: 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java?rev=1580656&view=auto
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java
 (added)
+++ 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java
 Sun Mar 23 23:02:46 2014
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.observer;
+
[email protected](java.lang.annotation.RetentionPolicy.RUNTIME)
[email protected]({ java.lang.annotation.ElementType.METHOD })
+public @interface Assert {
+    String[] value();
+}

Propchange: 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Assert.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/ObserverFeaturesTest.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/ObserverFeaturesTest.java?rev=1580656&r1=1580655&r2=1580656&view=diff
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/ObserverFeaturesTest.java
 (original)
+++ 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/ObserverFeaturesTest.java
 Sun Mar 23 23:02:46 2014
@@ -18,6 +18,7 @@ package org.apache.openejb.observer;
 
 import org.apache.openejb.observer.event.AfterEvent;
 import org.apache.openejb.observer.event.BeforeEvent;
+import org.apache.openejb.observer.event.ObserverFailed;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -27,7 +28,8 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-import static org.junit.Assert.assertEquals;
+import static org.apache.openejb.observer.Util.caller;
+import static org.apache.openejb.observer.Util.description;
 
 public class ObserverFeaturesTest {
 
@@ -235,6 +237,82 @@ public class ObserverFeaturesTest {
         }, 42, new Date(), URI.create("foo:bar"));
     }
 
+    @Test
+    @Assert({ "number", "failed" })
+    public void failure() {
+        a(new Object() {
+            public void number(final @Observes Integer event) {
+                invoked();
+                throw new RuntimeException("testing exceptions");
+            }
+
+            public void failed(final @Observes ObserverFailed event) {
+                invoked();
+            }
+        }, 42);
+    }
+
+    @Test
+    @Assert({ "number", "failed" })
+    public void circularFailureDirect() {
+        a(new Object() {
+            public void number(final @Observes Integer event) {
+                invoked();
+                throw new RuntimeException("testing exceptions");
+            }
+
+            public void failed(final @Observes ObserverFailed event) {
+                invoked();
+                throw new RuntimeException("testing exceptions");
+            }
+        }, 42);
+    }
+
+    @Test
+    @Assert({
+            "number.Integer",
+            "afterObject.AfterEvent<ObserverFailed{number}>",
+            "afterObject.AfterEvent<ObserverFailed{afterObject}>",
+            "afterObject.AfterEvent<Integer>",
+    })
+    public void circularFailureAfterObject() {
+        a(new Object() {
+            public void number(final @Observes Integer event) {
+                invoked(description(event));
+                throw new RuntimeException("testing exceptions");
+            }
+
+            public void afterObject(final @Observes AfterEvent<Object> event) {
+                invoked(description(event));
+                throw new RuntimeException("testing exceptions");
+            }
+        }, 42);
+    }
+
+    @Test
+    @Assert({
+            "number.Integer",
+            "afterObject.AfterEvent<Integer>",
+            "failed.ObserverFailed{afterObject}",
+            "afterObject.AfterEvent<ObserverFailed{afterObject}>",
+    })
+    public void circluarFailureProtection() {
+        a(new Object() {
+            public void number(final @Observes Integer event) {
+                invoked(description(event));
+            }
+
+            public void afterObject(final @Observes AfterEvent<Object> event) {
+                invoked(description(event));
+                throw new RuntimeException("testing exceptions");
+            }
+
+            public void failed(final @Observes ObserverFailed event) {
+                invoked(description(event));
+                throw new RuntimeException("testing exceptions");
+            }
+        }, 42);
+    }
 
     private List<Boolean> conditions = new ArrayList<Boolean>();
     private List<String> invocations = new ArrayList<String>();
@@ -257,6 +335,11 @@ public class ObserverFeaturesTest {
         invocations.add(method.getName());
     }
 
+    public void invoked(String suffix) {
+        final Method method = caller(2);
+        invocations.add(method.getName() + "." + suffix);
+    }
+
     private void a(final Object observer, Object... events) {
         final ObserverManager observers = new ObserverManager();
         observers.addObserver(observer);
@@ -283,31 +366,5 @@ public class ObserverFeaturesTest {
         }
     }
 
-    private Method caller(final int i) {
-        try {
-            final StackTraceElement[] stackTrace = new 
Exception().fillInStackTrace().getStackTrace();
-            final String methodName = stackTrace[i].getMethodName();
-            final String className = stackTrace[i].getClassName();
-
-            final Class<?> clazz = 
this.getClass().getClassLoader().loadClass(className);
-            for (Method method : clazz.getDeclaredMethods()) {
-                if (methodName.endsWith(method.getName())) {
-                    return method;
-                }
-            }
-
-            throw new NoSuchMethodException(methodName);
-        } catch (NoSuchMethodException e) {
-            throw new RuntimeException(e);
-        } catch (ClassNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
-    @java.lang.annotation.Target({ java.lang.annotation.ElementType.METHOD })
-    public @interface Assert {
-        String[] value();
-    }
 
 }

Modified: 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Util.java
URL: 
http://svn.apache.org/viewvc/tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Util.java?rev=1580656&r1=1580655&r2=1580656&view=diff
==============================================================================
--- 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Util.java
 (original)
+++ 
tomee/tomee/trunk/container/openejb-loader/src/test/java/org/apache/openejb/observer/Util.java
 Sun Mar 23 23:02:46 2014
@@ -16,6 +16,11 @@
  */
 package org.apache.openejb.observer;
 
+import org.apache.openejb.observer.event.AfterEvent;
+import org.apache.openejb.observer.event.BeforeEvent;
+import org.apache.openejb.observer.event.ObserverFailed;
+
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -43,4 +48,39 @@ public class Util {
     static void assertEvent(List<String> observed, String... expected) {
         assertEquals(join(expected), join(observed));
     }
+
+    static Method caller(final int i) {
+        try {
+            final StackTraceElement[] stackTrace = new 
Exception().fillInStackTrace().getStackTrace();
+            final String methodName = stackTrace[i].getMethodName();
+            final String className = stackTrace[i].getClassName();
+
+            final Class<?> clazz = 
Util.class.getClassLoader().loadClass(className);
+            for (Method method : clazz.getDeclaredMethods()) {
+                if (methodName.endsWith(method.getName())) {
+                    return method;
+                }
+            }
+
+            throw new NoSuchMethodException(methodName);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static String description(Object event) {
+        if (event instanceof ObserverFailed) {
+            ObserverFailed observerFailed = (ObserverFailed) event;
+            return "ObserverFailed{" + observerFailed.getMethod().getName() + 
"}";
+        }
+        if (event instanceof BeforeEvent) {
+            return "BeforeEvent<" + description(((BeforeEvent) 
event).getEvent()) + ">";
+        }
+        if (event instanceof AfterEvent) {
+            return "AfterEvent<" + description(((AfterEvent) 
event).getEvent()) + ">";
+        }
+        return event.getClass().getSimpleName();
+    }
 }


Reply via email to