code.onehippo.org is currently readonly. We are migrating to code.bloomreach.com, please continue working there on Monday 14/12. See: https://docs.bloomreach.com/display/engineering/GitLab

Commit f99e1661 authored by Ate Douma's avatar Ate Douma

CMS7-7672: Get rid of setAccessibility on private field in hippo services eventbus

This provides a completely new solution using ASM generated proxies
parent 9ccb925c
* text=auto !eol
/LICENSE -text
/NOTICE -text
src/main/java/com/google/common/eventbus/HippoAnnotationHandlerFinder.java -text
src/test/java/org/onehippo/cms7/services/eventbus/HippoAnnotationHandlerFinderTest.java -text svneol=unset#text/plain
......@@ -36,6 +36,8 @@
<hippo.services.version>1.03.04-SNAPSHOT</hippo.services.version>
<hippo.commons.version>1.03.01-SNAPSHOT</hippo.commons.version>
<asm.version>4.2</asm.version>
</properties>
<scm>
......@@ -76,6 +78,12 @@
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
......
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
*
* 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.google.common.eventbus;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.onehippo.cms7.services.HippoServiceRegistration;
import org.onehippo.cms7.services.eventbus.Subscribe;
/**
* Fork of the guava AnnotationHandlerFinder that is injected by reflection.
* The guava handler finding strategy api is not yet public, but uses package security.
*/
public class HippoAnnotationHandlerFinder implements HandlerFindingStrategy {
@Override
public Multimap<Class<?>, EventHandler> findAllHandlers(Object registered) {
Multimap<Class<?>, EventHandler> methodsInListener = HashMultimap.create();
Object listener;
ClassLoader classLoader;
if (registered instanceof HippoServiceRegistration) {
HippoServiceRegistration registration = (HippoServiceRegistration) registered;
listener = registration.getService();
classLoader = registration.getClassLoader();
} else {
listener = registered;
classLoader = Thread.currentThread().getContextClassLoader();
}
Class<?> clazz = listener.getClass();
// loop through all the classes' methods
for (Method method : clazz.getMethods()) {
Method annotatedMethod = doGetAnnotatedMethod(method, Subscribe.class);
if (annotatedMethod != null) {
// the method has a Subscribe annotation
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException(
"Method " + method + " has @Subscribe annotation, but requires " +
parameterTypes.length + " arguments. Event handler methods " +
"must require a single argument.");
}
Class<?> eventType = parameterTypes[0];
if (acceptMethod(listener, method.getAnnotations(), eventType)) {
EventHandler handler = new HippoSynchronizedEventHandler(listener, method, classLoader);
methodsInListener.put(eventType, handler);
}
}
}
return methodsInListener;
}
protected boolean acceptMethod(Object listener, Annotation[] annotations, Class<?> parameterType) {
return true;
}
/**
* returns the annotated method with annotation clazz and null if the clazz annotation is not present
* @param m
* @param clazz the annotation to look for
* @return the {@link Method} that contains the annotation <code>clazz</code> and <code>null</code> if none found
*/
private static Method doGetAnnotatedMethod(final Method m, Class<Subscribe> clazz) {
if (m == null) {
return m;
}
Subscribe annotation = m.getAnnotation(clazz);
if(annotation != null ) {
// found Subscribe annotation
return m;
}
Class<?> superC = m.getDeclaringClass().getSuperclass();
if (superC != null && Object.class != superC) {
try {
Method method = doGetAnnotatedMethod(superC.getMethod(m.getName(), m.getParameterTypes()), clazz);
if (method != null) {
return method;
}
} catch (NoSuchMethodException ex) {
// ignore
}
}
for (Class<?> i : m.getDeclaringClass().getInterfaces()) {
try {
Method method = doGetAnnotatedMethod(i.getMethod(m.getName(), m.getParameterTypes()), clazz);
if (method != null) {
return method;
}
} catch (NoSuchMethodException ex) {
// ignore
}
}
return null;
}
}
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
*
* 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.google.common.eventbus;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Event handler that sets up the contex classloader when dispatching events.
*/
public class HippoSynchronizedEventHandler extends SynchronizedEventHandler {
private final Annotation[] annotations;
private final ClassLoader classLoader;
public HippoSynchronizedEventHandler(final Object target, final Method method, ClassLoader contextClassLoader) {
super(target, method);
this.annotations = method.getAnnotations();
this.classLoader = contextClassLoader;
}
public Annotation[] getAnnotations() {
if (annotations == null) {
return new Annotation[0];
}
return annotations.clone();
}
@Override
public synchronized void handleEvent(final Object event) throws InvocationTargetException {
ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
super.handleEvent(event);
} finally {
Thread.currentThread().setContextClassLoader(currentContextClassLoader);
}
}
}
/*
* Copyright 2014 Hippo B.V. (http://www.onehippo.com)
*
* 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 org.onehippo.cms7.services.eventbus;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Base class for dynamically generated proxy classes which are used as wrapper for Guava EventBus registration through
* the {@link GuavaHippoEventBus}.
* <p>
* The creation and caching of both generated proxy classes and instances is managed through the
* {@link org.onehippo.cms7.services.eventbus.GuavaEventBusListenerProxyFactory}.
* </p>
* @see GuavaHippoEventBus
* @see GuavaEventBusListenerProxyFactory
*/
public abstract class GuavaEventBusListenerProxy implements Cloneable {
private volatile Object listener;
private ClassLoader cl;
private Method[] methods;
protected GuavaEventBusListenerProxy(Object listener, ClassLoader cl, Method[] methods) {
this.listener = listener;
this.cl = cl;
this.methods = methods;
}
protected void handleEvent(int methodIndex, Object event) throws InvocationTargetException {
ClassLoader lcl = cl;
Method m = methods[methodIndex];
Object ll = listener;
// protect against concurrent called destroy
if (listener != null) {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(lcl);
m.invoke(ll, event);
} catch (IllegalArgumentException e) {
throw new Error("Method rejected target/argument: " + event, e);
} catch (IllegalAccessException e) {
throw new Error("Method became inaccessible: " + event, e);
} catch (InvocationTargetException e) {
e.printStackTrace();
if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
}
throw e;
}
finally {
Thread.currentThread().setContextClassLoader(ccl);
}
}
}
GuavaEventBusListenerProxy clone(Object listener) {
try {
GuavaEventBusListenerProxy clone = (GuavaEventBusListenerProxy)super.clone();
clone.listener = listener;
return clone;
} catch (CloneNotSupportedException e) {
// will never happen
}
return null;
}
/**
* Clears the proxy wrapped listener, its classloader and its wrapped methods.
* The proxy can no longer be used after this, other than to unregister from the Guava EventBus!
*/
void destroy() {
listener = null;
cl = null;
for (int i = 0; i < methods.length; i++) {
methods[i] = null;
}
}
}
......@@ -15,8 +15,6 @@
*/
package org.onehippo.cms7.services.eventbus;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
......@@ -25,11 +23,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.HippoAnnotationHandlerFinder;
import org.onehippo.cms7.event.HippoEvent;
import org.onehippo.cms7.services.HippoServiceException;
import org.onehippo.cms7.services.HippoServiceRegistration;
import org.onehippo.cms7.services.HippoServiceRegistry;
import org.slf4j.Logger;
......@@ -37,53 +32,37 @@ import org.slf4j.LoggerFactory;
public class GuavaHippoEventBus implements HippoEventBus {
static final Logger log = LoggerFactory.getLogger(GuavaHippoEventBus.class);
private static final Logger log = LoggerFactory.getLogger(GuavaHippoEventBus.class);
final ExecutorService executor = Executors.newSingleThreadExecutor();
final AsyncEventBus eventBus = new AsyncEventBus(executor);
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final AsyncEventBus eventBus = new AsyncEventBus(executor);
final List<HippoServiceRegistration> listeners = Collections.synchronizedList(new ArrayList<HippoServiceRegistration>());
private final List<HippoServiceRegistration> listeners = Collections.synchronizedList(new ArrayList<HippoServiceRegistration>());
private final GuavaEventBusListenerProxyFactory proxyFactory = new GuavaEventBusListenerProxyFactory();
private volatile int version = -1;
public GuavaHippoEventBus() {
try {
Field finderField = EventBus.class.getDeclaredField("finder");
finderField.setAccessible(true);
finderField.set(eventBus, new HippoAnnotationHandlerFinder() {
@Override
protected boolean acceptMethod(final Object listener, final Annotation[] annotations, final Class<?> parameterType) {
return GuavaHippoEventBus.this.acceptMethod(listener, annotations, parameterType);
}
});
} catch (NoSuchFieldException e) {
throw new HippoServiceException("Unable to initialize event bus", e);
} catch (IllegalAccessException e) {
throw new HippoServiceException("Unable to initialize event bus", e);
}
}
protected boolean acceptMethod(final Object listener, final Annotation[] annotations, final Class<?> parameterType) {
return true;
}
public void destroy() {
for (GuavaEventBusListenerProxy proxy : proxyFactory.clear()) {
eventBus.unregister(proxy);
}
listeners.clear();
executor.shutdown();
}
@Override
public void register(final Object listener) {
eventBus.register(listener);
registerProxy(listener);
log.warn("HippoEventBus method #register is deprecated, use whiteboard pattern instead");
}
@Override
public void unregister(final Object listener) {
eventBus.unregister(listener);
unregisterProxy(listener);
}
public void post(final Object event) {
if (updateListenersNeeded()) {
if (version != HippoServiceRegistry.getVersion()) {
updateListeners();
}
if (event instanceof HippoEvent) {
......@@ -92,19 +71,27 @@ public class GuavaHippoEventBus implements HippoEventBus {
eventBus.post(event);
}
private boolean updateListenersNeeded() {
if (version == HippoServiceRegistry.getVersion()) {
return false;
protected void unregisterProxy(final Object listener) {
GuavaEventBusListenerProxy proxy = proxyFactory.removeProxy(listener);
if (proxy != null) {
eventBus.unregister(proxy);
}
}
protected void registerProxy(final Object listener) {
GuavaEventBusListenerProxy proxy = proxyFactory.createProxy(listener);
if (proxy != null) {
eventBus.register(proxy);
}
return true;
}
private void updateListeners() {
List<HippoServiceRegistration> registered = getServiceRegistrations();
List<HippoServiceRegistration> registered = HippoServiceRegistry.getRegistrations(HippoEventBus.class);
for (HippoServiceRegistration registration : registered) {
if (!listeners.contains(registration)) {
listeners.add(registration);
eventBus.register(registration);
registerProxy(registration);
}
}
Iterator<HippoServiceRegistration> iterator = listeners.iterator();
......@@ -112,13 +99,9 @@ public class GuavaHippoEventBus implements HippoEventBus {
HippoServiceRegistration registration = iterator.next();
if (!registered.contains(registration)) {
iterator.remove();
eventBus.unregister(registration);
unregisterProxy(registration);
}
}
version = HippoServiceRegistry.getVersion();
}
protected List<HippoServiceRegistration> getServiceRegistrations() {
return HippoServiceRegistry.getRegistrations(HippoEventBus.class);
}
}
......@@ -37,7 +37,7 @@ public class GuavaHippoEventBusTest {
@Test
public void testEventBusWithDirectListener() throws InterruptedException {
HippoEventBus eventBus = new GuavaHippoEventBus();
GuavaHippoEventBus eventBus = new GuavaHippoEventBus();
Listener listener = new Listener();
eventBus.register(listener);
eventBus.post(new Object());
......
/*
* Copyright 2012-2013 Hippo B.V. (http://www.onehippo.com)
*
* 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 org.onehippo.cms7.services.eventbus;
import static junit.framework.Assert.assertTrue;
import org.junit.Test;
import org.onehippo.cms7.event.HippoEvent;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.HippoAnnotationHandlerFinder;
public class HippoAnnotationHandlerFinderTest {
@Test
public void testAnonymousAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new Object() {
@Subscribe
public void thisIsAHandler(HippoEvent<?> event) {
// do nothing
}
});
assertTrue(map.size() == 1);
}
@Test
public void testDirectAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new A());
assertTrue(map.size() == 2);
}
@Test
public void testInheritedAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new B());
assertTrue(map.size() == 2);
}
@Test
public void testInheritedAnnotationsWithOverrdeWithoutAnnotationFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new C());
// overrding a method and then not including the annotation should take the annotation
// from the overriden class
assertTrue(map.size() == 2);
}
@Test
public void testInterfaceAnnonymousAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new I() {
@Override
public void thisIsAHandler(HippoEvent<?> event) {
// do nothing
}
});
assertTrue(map.size() == 1);
}
@Test
public void testInterfaceAnnonymousAnnotationWithExtraSubscribeMethodFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new I() {
@Override
public void thisIsAHandler(HippoEvent<?> event) {
// do nothing
}
@Subscribe
public void testExtra(HippoEvent<?> event) {
//
}
});
// added an extra Subscribe method
assertTrue(map.size() == 2);
}
@Test
public void testInterfaceImplAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new Impl() {
});
assertTrue(map.size() == 1);
}
@Test
public void testInterfaceSubImplWithExtraInterfaceAnnotationsFinder() {
HippoAnnotationHandlerFinder finder = new HippoAnnotationHandlerFinder();
Multimap map = finder.findAllHandlers(new SubImpl() {
});
// we have now 2 Subscribe annotations through two different interfaces
assertTrue(map.size() == 2);
}
public class SubImpl extends Impl implements I2 {
@Override
public void thisIsAHandler(HippoEvent<?> event) {
// nothing
}
@Override
public void thisIsASecondHandler(HippoEvent<?> event) {
// nothing
}
}
public class Impl implements I {
@Override
public void thisIsAHandler(HippoEvent<?> event) {
// nothing
}
}
public interface I {
@Subscribe
public void thisIsAHandler(HippoEvent<?> event);
}
public interface I2 {
@Subscribe
public void thisIsASecondHandler(HippoEvent<?> event);
}
public class A extends B {
}
public class C extends B {
@Override
public void thisIsAHandler(HippoEvent<?> event) {
}
}
public class B {
@Subscribe
public void thisIsAHandler(HippoEvent<?> event) {
// do nothing
}
@Subscribe
public void thisIsASecondHandler(HippoEvent<?> event) {
// do nothing
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment