blob: 6fe8b4ebfbee371c0c28c6fe7b401ba6977134c6 [file] [log] [blame]
/**
* Copyright (C) 2008 Google Inc.
*
* 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.inject;
import static com.google.inject.matcher.Matchers.only;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.ConstructorBinding;
import junit.framework.TestCase;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author jessewilson@google.com (Jesse Wilson)
*/
public class MethodInterceptionTest extends TestCase {
private AtomicInteger count = new AtomicInteger();
private final class CountingInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
count.incrementAndGet();
return methodInvocation.proceed();
}
}
private final class ReturnNullInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return null;
}
}
private final class NoOpInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return methodInvocation.proceed();
}
}
public void testSharedProxyClasses() {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
new ReturnNullInterceptor());
}
});
Injector childOne = injector.createChildInjector(new AbstractModule() {
protected void configure() {
bind(Interceptable.class);
}
});
Interceptable nullFoosOne = childOne.getInstance(Interceptable.class);
assertNotNull(nullFoosOne.bar());
assertNull(nullFoosOne.foo()); // confirm it's being intercepted
Injector childTwo = injector.createChildInjector(new AbstractModule() {
protected void configure() {
bind(Interceptable.class);
}
});
Interceptable nullFoosTwo = childTwo.getInstance(Interceptable.class);
assertNull(nullFoosTwo.foo()); // confirm it's being intercepted
assertSame("Child injectors should share proxy classes, otherwise memory leaks!",
nullFoosOne.getClass(), nullFoosTwo.getClass());
Injector injector2 = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
new ReturnNullInterceptor());
}
});
Interceptable separateNullFoos = injector2.getInstance(Interceptable.class);
assertNull(separateNullFoos.foo()); // confirm it's being intercepted
assertSame("different injectors should share proxy classes, otherwise memory leaks!",
nullFoosOne.getClass(), separateNullFoos.getClass());
}
public void testGetThis() {
final AtomicReference<Object> lastTarget = new AtomicReference<Object>();
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
lastTarget.set(methodInvocation.getThis());
return methodInvocation.proceed();
}
});
}
});
Interceptable interceptable = injector.getInstance(Interceptable.class);
interceptable.foo();
assertSame(interceptable, lastTarget.get());
}
public void testInterceptingFinalClass() {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
return methodInvocation.proceed();
}
});
}
});
try {
injector.getInstance(NotInterceptable.class);
fail();
} catch(ConfigurationException ce) {
assertEquals("Unable to method intercept: " + NotInterceptable.class.getName(),
Iterables.getOnlyElement(ce.getErrorMessages()).getMessage().toString());
assertEquals("Cannot subclass final class class " + NotInterceptable.class.getName(),
ce.getCause().getMessage());
}
}
public void testSpiAccessToInterceptors() throws NoSuchMethodException {
final MethodInterceptor countingInterceptor = new CountingInterceptor();
final MethodInterceptor returnNullInterceptor = new ReturnNullInterceptor();
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(),Matchers.returns(only(Foo.class)),
countingInterceptor);
bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class).or(only(Bar.class))),
returnNullInterceptor);
}
});
ConstructorBinding<?> interceptedBinding
= (ConstructorBinding<?>) injector.getBinding(Interceptable.class);
Method barMethod = Interceptable.class.getMethod("bar");
Method fooMethod = Interceptable.class.getMethod("foo");
assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(
fooMethod, ImmutableList.of(countingInterceptor, returnNullInterceptor),
barMethod, ImmutableList.of(returnNullInterceptor)),
interceptedBinding.getMethodInterceptors());
ConstructorBinding<?> nonInterceptedBinding
= (ConstructorBinding<?>) injector.getBinding(Foo.class);
assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(),
nonInterceptedBinding.getMethodInterceptors());
injector.getInstance(Interceptable.class).foo();
assertEquals("expected counting interceptor to be invoked first", 1, count.get());
}
public void testInterceptedMethodThrows() throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
}
});
Interceptable interceptable = injector.getInstance(Interceptable.class);
try {
interceptable.explode();
fail();
} catch (Exception e) {
// validate all causes.
for (Throwable t = e; t != null; t = t.getCause()) {
StackTraceElement[] stackTraceElement = t.getStackTrace();
assertEquals("explode", stackTraceElement[0].getMethodName());
assertEquals("invoke", stackTraceElement[1].getMethodName());
assertEquals("invoke", stackTraceElement[2].getMethodName());
assertEquals("testInterceptedMethodThrows", stackTraceElement[3].getMethodName());
}
}
}
public void testNotInterceptedMethodsInInterceptedClassDontAddFrames() {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
new NoOpInterceptor());
}
});
Interceptable interceptable = injector.getInstance(Interceptable.class);
assertNull(interceptable.lastElements);
interceptable.foo();
boolean cglibFound = false;
for (int i = 0; i < interceptable.lastElements.length; i++) {
if (interceptable.lastElements[i].toString().contains("cglib")) {
cglibFound = true;
break;
}
}
assertTrue(Arrays.asList(interceptable.lastElements).toString(), cglibFound);
cglibFound = false;
interceptable.bar();
for (int i = 0; i < interceptable.lastElements.length; i++) {
if (interceptable.lastElements[i].toString().contains("cglib")) {
cglibFound = true;
break;
}
}
assertFalse(Arrays.asList(interceptable.lastElements).toString(), cglibFound);
}
static class Foo {}
static class Bar {}
public static class Interceptable {
StackTraceElement[] lastElements;
public Foo foo() {
lastElements = Thread.currentThread().getStackTrace();
return new Foo() {};
}
public Bar bar() {
lastElements = Thread.currentThread().getStackTrace();
return new Bar() {};
}
public String explode() throws Exception {
lastElements = Thread.currentThread().getStackTrace();
throw new Exception("kaboom!", new RuntimeException("boom!"));
}
}
public static final class NotInterceptable {}
public void testInterceptingNonBridgeWorks() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Interface.class).to(Impl.class);
bindInterceptor(Matchers.any(), new AbstractMatcher<Method>() {
public boolean matches(Method t) {
return !t.isBridge() && t.getDeclaringClass() != Object.class;
}
}, new CountingInterceptor());
}
});
Interface intf = injector.getInstance(Interface.class);
assertEquals(0, count.get());
intf.aMethod(null);
assertEquals(1, count.get());
}
static class ErasedType {}
static class RetType extends ErasedType {}
static abstract class Superclass<T extends ErasedType> {
public T aMethod(T t) { return null; }
}
public interface Interface {
RetType aMethod(RetType obj);
}
public static class Impl extends Superclass<RetType> implements Interface {
}
public void testInterceptionOrder() {
final List<String> callList = Lists.newArrayList();
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.any(),
new NamedInterceptor("a", callList),
new NamedInterceptor("b", callList),
new NamedInterceptor("c", callList));
}
});
Interceptable interceptable = injector.getInstance(Interceptable.class);
assertEquals(0, callList.size());
interceptable.foo();
assertEquals(Arrays.asList("a", "b", "c"), callList);
}
private final class NamedInterceptor implements MethodInterceptor {
private final String name;
final List<String> called;
NamedInterceptor(String name, List<String> callList) {
this.name = name;
this.called = callList;
}
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
called.add(name);
return methodInvocation.proceed();
}
}
}