Skip to content

Commit

Permalink
ISPN-1220 Make sure that ClassLoader-decorated caches set the correct
Browse files Browse the repository at this point in the history
classloader when invoking listeners
  • Loading branch information
tristantarrant authored and galderz committed Jan 8, 2013
1 parent bfe1f52 commit 8ee0b38
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 44 deletions.
36 changes: 0 additions & 36 deletions core/src/main/java/org/infinispan/ClassLoaderSpecfiedCache.java

This file was deleted.

16 changes: 13 additions & 3 deletions core/src/main/java/org/infinispan/DecoratedCache.java
Expand Up @@ -19,8 +19,7 @@

package org.infinispan;

import org.infinispan.context.Flag;
import org.infinispan.util.concurrent.NotifyingFuture;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.util.Arrays;
import java.util.Collection;
Expand All @@ -30,7 +29,9 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import org.infinispan.context.Flag;
import org.infinispan.notifications.ClassLoaderAwareListenable;
import org.infinispan.util.concurrent.NotifyingFuture;

/**
* A decorator to a cache, which can be built with a specific {@link ClassLoader} and a set of {@link Flag}s. This
Expand Down Expand Up @@ -373,4 +374,13 @@ public V replace(K key, V value) {
public EnumSet<Flag> getFlags() {
return flags;
}

@Override
public void addListener(Object listener) {
if (cacheImplementation.notifier instanceof ClassLoaderAwareListenable) {
((ClassLoaderAwareListenable)cacheImplementation.notifier).addListener(listener, classLoader);
} else {
throw new IllegalStateException("The CacheNotifier does not implement the ClassLoaderAwareListenable interface");
}
}
}
Expand Up @@ -36,6 +36,8 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -108,7 +110,11 @@ private void removeListenerInvocation(Class<? extends Annotation> annotation, Ob
}

public void addListener(Object listener) {
validateAndAddListenerInvocation(listener);
addListener(listener, null);
}

public void addListener(Object listener, ClassLoader classLoader) {
validateAndAddListenerInvocation(listener, classLoader);
}

public Set<Object> getListeners() {
Expand All @@ -126,7 +132,7 @@ public Set<Object> getListeners() {
* @param listener object to be considered as a listener.
*/
@SuppressWarnings("unchecked")
private void validateAndAddListenerInvocation(Object listener) {
private void validateAndAddListenerInvocation(Object listener, ClassLoader classLoader) {
boolean sync = testListenerClassValidity(listener.getClass());
boolean foundMethods = false;
Map<Class<? extends Annotation>, Class<?>> allowedListeners = getAllowedMethodAnnotations();
Expand All @@ -138,7 +144,7 @@ private void validateAndAddListenerInvocation(Object listener) {
Class<?> value = annotationEntry.getValue();
if (m.isAnnotationPresent(key)) {
testListenerMethodValidity(m, value, key.getName());
addListenerInvocation(key, new ListenerInvocation(listener, m, sync));
addListenerInvocation(key, new ListenerInvocation(listener, m, sync, classLoader));
foundMethods = true;
}
}
Expand Down Expand Up @@ -184,18 +190,24 @@ protected class ListenerInvocation {
public final Object target;
public final Method method;
public final boolean sync;
public final ClassLoader classLoader;

public ListenerInvocation(Object target, Method method, boolean sync) {
public ListenerInvocation(Object target, Method method, boolean sync, ClassLoader classLoader) {
this.target = target;
this.method = method;
this.sync = sync;
this.classLoader = classLoader;
}

public void invoke(final Object event) {
Runnable r = new Runnable() {

@Override
public void run() {
ClassLoader contextClassLoader = null;
if (classLoader != null) {
contextClassLoader = setContextClassLoader(classLoader);
}
try {
method.invoke(target, event);
}
Expand All @@ -213,6 +225,10 @@ public void run() {
catch (IllegalAccessException exception) {
getLog().unableToInvokeListenerMethod(method, target, exception);
removeListener(target);
} finally {
if (classLoader != null) {
setContextClassLoader(contextClassLoader);
}
}
}
};
Expand All @@ -233,4 +249,15 @@ private Throwable getRealException(Throwable re) {
return re;
}

static ClassLoader setContextClassLoader(final ClassLoader loader) {
PrivilegedAction<ClassLoader> action = new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(loader);
return contextClassLoader;
}
};
return AccessController.doPrivileged(action);
}
}
@@ -0,0 +1,31 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.infinispan.notifications;

/**
* Interface that enhances {@link Listenable} with the possibility of specifying the
* {@link ClassLoader} which should be set as the context class loader for the invoked
* listener method
*
* @author Tristan Tarrant
* @since 5.2
*/
public interface ClassLoaderAwareListenable extends Listenable {
public void addListener(Object listener, ClassLoader classLoader);
}
Expand Up @@ -29,6 +29,7 @@
import org.infinispan.distribution.ch.ConsistentHash;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.notifications.AbstractListenerImpl;
import org.infinispan.notifications.ClassLoaderAwareListenable;
import org.infinispan.notifications.cachelistener.annotation.*;
import org.infinispan.notifications.cachelistener.event.*;
import org.infinispan.remoting.transport.Address;
Expand All @@ -55,7 +56,7 @@
* @author Mircea.Markus@jboss.com
* @since 4.0
*/
public final class CacheNotifierImpl extends AbstractListenerImpl implements CacheNotifier {
public final class CacheNotifierImpl extends AbstractListenerImpl implements CacheNotifier, ClassLoaderAwareListenable {

private static final Log log = LogFactory.getLog(CacheNotifierImpl.class);

Expand Down
@@ -0,0 +1,101 @@
/*
* JBoss, Home of Professional Open Source
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.infinispan.notifications.cachelistener;

import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.loaders.dummy.DummyInMemoryCacheStoreConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryActivated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryLoaded;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryPassivated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryVisited;
import org.infinispan.notifications.cachelistener.event.Event;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.annotations.Test;

import static org.testng.AssertJUnit.*;

/**
* CustomClassLoaderListenerTest.
*
* @author Tristan Tarrant
* @since 5.2
*/
@Test(groups = "functional", testName = "notifications.cachelistener.CustomClassLoaderListenerTest")
public class CustomClassLoaderListenerTest extends SingleCacheManagerTest {

private CustomClassLoader ccl;

@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
ConfigurationBuilder builder = getDefaultStandaloneCacheConfig(false);
builder.loaders().passivation(true).addStore(DummyInMemoryCacheStoreConfigurationBuilder.class);

return TestCacheManagerFactory.createCacheManager(builder);
}

public void testCustomClassLoaderListener() throws Exception {
ccl = new CustomClassLoader(Thread.currentThread().getContextClassLoader());
ClassLoaderListener listener = new ClassLoaderListener();
cache().getAdvancedCache().with(ccl).addListener(listener);
cache().put("a", "a"); // Created + Modified
cache().replace("a", "b"); // Modified

cache().evict("a"); // Passivated + Evicted

cache().get("a"); // Loaded + Activated + Visited

cache().remove("a"); // Removed


assertEquals(9, listener.invocationCount);
}

public static class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
}

@Listener
public class ClassLoaderListener {
int invocationCount = 0;

@CacheEntryActivated
@CacheEntryCreated
@CacheEntriesEvicted
@CacheEntryLoaded
@CacheEntryModified
@CacheEntryPassivated
@CacheEntryRemoved
@CacheEntryVisited
public void handle(Event e) {
assertEquals(ccl, Thread.currentThread().getContextClassLoader());
if(!e.isPre()) {
++invocationCount;
}
}
}
}

0 comments on commit 8ee0b38

Please sign in to comment.