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();
+ }
}