Home | History | Annotate | Download | only in inject
      1 /**
      2  * Copyright (C) 2008 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject;
     18 
     19 import static com.google.inject.matcher.Matchers.only;
     20 
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.common.collect.ImmutableMap;
     23 import com.google.common.collect.Iterables;
     24 import com.google.common.collect.Lists;
     25 import com.google.inject.matcher.AbstractMatcher;
     26 import com.google.inject.matcher.Matchers;
     27 import com.google.inject.spi.ConstructorBinding;
     28 
     29 import junit.framework.TestCase;
     30 
     31 import org.aopalliance.intercept.MethodInterceptor;
     32 import org.aopalliance.intercept.MethodInvocation;
     33 
     34 import java.lang.reflect.Method;
     35 import java.util.Arrays;
     36 import java.util.List;
     37 import java.util.Queue;
     38 import java.util.concurrent.atomic.AtomicInteger;
     39 import java.util.concurrent.atomic.AtomicReference;
     40 
     41 /**
     42  * @author jessewilson (at) google.com (Jesse Wilson)
     43  */
     44 public class MethodInterceptionTest extends TestCase {
     45 
     46   private AtomicInteger count = new AtomicInteger();
     47 
     48   private final class CountingInterceptor implements MethodInterceptor {
     49     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
     50       count.incrementAndGet();
     51       return methodInvocation.proceed();
     52     }
     53   }
     54 
     55   private final class ReturnNullInterceptor implements MethodInterceptor {
     56     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
     57       return null;
     58     }
     59   }
     60 
     61   private final class NoOpInterceptor implements MethodInterceptor {
     62     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
     63       return methodInvocation.proceed();
     64     }
     65   }
     66 
     67   public void testSharedProxyClasses() {
     68     Injector injector = Guice.createInjector(new AbstractModule() {
     69       protected void configure() {
     70         bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
     71             new ReturnNullInterceptor());
     72       }
     73     });
     74 
     75     Injector childOne = injector.createChildInjector(new AbstractModule() {
     76       protected void configure() {
     77         bind(Interceptable.class);
     78       }
     79     });
     80 
     81     Interceptable nullFoosOne = childOne.getInstance(Interceptable.class);
     82     assertNotNull(nullFoosOne.bar());
     83     assertNull(nullFoosOne.foo()); // confirm it's being intercepted
     84 
     85     Injector childTwo = injector.createChildInjector(new AbstractModule() {
     86       protected void configure() {
     87         bind(Interceptable.class);
     88       }
     89     });
     90 
     91     Interceptable nullFoosTwo = childTwo.getInstance(Interceptable.class);
     92     assertNull(nullFoosTwo.foo()); // confirm it's being intercepted
     93 
     94     assertSame("Child injectors should share proxy classes, otherwise memory leaks!",
     95         nullFoosOne.getClass(), nullFoosTwo.getClass());
     96 
     97     Injector injector2 = Guice.createInjector(new AbstractModule() {
     98       protected void configure() {
     99         bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
    100             new ReturnNullInterceptor());
    101       }
    102     });
    103     Interceptable separateNullFoos = injector2.getInstance(Interceptable.class);
    104     assertNull(separateNullFoos.foo()); // confirm it's being intercepted
    105     assertSame("different injectors should share proxy classes, otherwise memory leaks!",
    106         nullFoosOne.getClass(), separateNullFoos.getClass());
    107   }
    108 
    109   public void testGetThis() {
    110     final AtomicReference<Object> lastTarget = new AtomicReference<Object>();
    111 
    112     Injector injector = Guice.createInjector(new AbstractModule() {
    113       protected void configure() {
    114         bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
    115           public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    116             lastTarget.set(methodInvocation.getThis());
    117             return methodInvocation.proceed();
    118           }
    119         });
    120       }
    121     });
    122 
    123     Interceptable interceptable = injector.getInstance(Interceptable.class);
    124     interceptable.foo();
    125     assertSame(interceptable, lastTarget.get());
    126   }
    127 
    128   public void testInterceptingFinalClass() {
    129     Injector injector = Guice.createInjector(new AbstractModule() {
    130       protected void configure() {
    131         bindInterceptor(Matchers.any(), Matchers.any(), new MethodInterceptor() {
    132           public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    133             return methodInvocation.proceed();
    134           }
    135         });
    136       }
    137     });
    138     try {
    139       injector.getInstance(NotInterceptable.class);
    140       fail();
    141     } catch(ConfigurationException ce) {
    142       assertEquals("Unable to method intercept: " + NotInterceptable.class.getName(),
    143           Iterables.getOnlyElement(ce.getErrorMessages()).getMessage().toString());
    144       assertEquals("Cannot subclass final class class " + NotInterceptable.class.getName(),
    145           ce.getCause().getMessage());
    146     }
    147   }
    148 
    149   public void testSpiAccessToInterceptors() throws NoSuchMethodException {
    150     final MethodInterceptor countingInterceptor = new CountingInterceptor();
    151     final MethodInterceptor returnNullInterceptor = new ReturnNullInterceptor();
    152     Injector injector = Guice.createInjector(new AbstractModule() {
    153       protected void configure() {
    154         bindInterceptor(Matchers.any(),Matchers.returns(only(Foo.class)),
    155             countingInterceptor);
    156         bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class).or(only(Bar.class))),
    157             returnNullInterceptor);
    158       }
    159     });
    160 
    161     ConstructorBinding<?> interceptedBinding
    162         = (ConstructorBinding<?>) injector.getBinding(Interceptable.class);
    163     Method barMethod = Interceptable.class.getMethod("bar");
    164     Method fooMethod = Interceptable.class.getMethod("foo");
    165     assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(
    166         fooMethod, ImmutableList.of(countingInterceptor, returnNullInterceptor),
    167         barMethod, ImmutableList.of(returnNullInterceptor)),
    168         interceptedBinding.getMethodInterceptors());
    169 
    170     ConstructorBinding<?> nonInterceptedBinding
    171         = (ConstructorBinding<?>) injector.getBinding(Foo.class);
    172     assertEquals(ImmutableMap.<Method, List<MethodInterceptor>>of(),
    173         nonInterceptedBinding.getMethodInterceptors());
    174 
    175     injector.getInstance(Interceptable.class).foo();
    176     assertEquals("expected counting interceptor to be invoked first", 1, count.get());
    177   }
    178 
    179   public void testInterceptedMethodThrows() throws Exception {
    180     Injector injector = Guice.createInjector(new AbstractModule() {
    181       protected void configure() {
    182         bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
    183         bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
    184       }
    185     });
    186 
    187     Interceptable interceptable = injector.getInstance(Interceptable.class);
    188     try {
    189       interceptable.explode();
    190       fail();
    191     } catch (Exception e) {
    192       // validate all causes.
    193       for (Throwable t = e; t != null; t = t.getCause()) {
    194         StackTraceElement[] stackTraceElement = t.getStackTrace();
    195         assertEquals("explode", stackTraceElement[0].getMethodName());
    196         assertEquals("invoke", stackTraceElement[1].getMethodName());
    197         assertEquals("invoke", stackTraceElement[2].getMethodName());
    198         assertEquals("testInterceptedMethodThrows", stackTraceElement[3].getMethodName());
    199       }
    200     }
    201   }
    202 
    203   public void testNotInterceptedMethodsInInterceptedClassDontAddFrames() {
    204     Injector injector = Guice.createInjector(new AbstractModule() {
    205       protected void configure() {
    206         bindInterceptor(Matchers.any(), Matchers.returns(only(Foo.class)),
    207             new NoOpInterceptor());
    208       }
    209     });
    210 
    211     Interceptable interceptable = injector.getInstance(Interceptable.class);
    212     assertNull(interceptable.lastElements);
    213     interceptable.foo();
    214     boolean cglibFound = false;
    215     for (int i = 0; i < interceptable.lastElements.length; i++) {
    216       if (interceptable.lastElements[i].toString().contains("cglib")) {
    217         cglibFound = true;
    218         break;
    219       }
    220     }
    221     assertTrue(Arrays.toString(interceptable.lastElements), cglibFound);
    222     cglibFound = false;
    223 
    224     interceptable.bar();
    225     for (int i = 0; i < interceptable.lastElements.length; i++) {
    226       if (interceptable.lastElements[i].toString().contains("cglib")) {
    227         cglibFound = true;
    228         break;
    229       }
    230     }
    231     assertFalse(Arrays.toString(interceptable.lastElements), cglibFound);
    232   }
    233 
    234   static class Foo {}
    235   static class Bar {}
    236 
    237   public static class Interceptable {
    238     StackTraceElement[] lastElements;
    239 
    240     public Foo foo() {
    241       lastElements = Thread.currentThread().getStackTrace();
    242       return new Foo() {};
    243     }
    244     public Bar bar() {
    245       lastElements = Thread.currentThread().getStackTrace();
    246       return new Bar() {};
    247     }
    248     public String explode() throws Exception {
    249       lastElements = Thread.currentThread().getStackTrace();
    250       throw new Exception("kaboom!", new RuntimeException("boom!"));
    251     }
    252   }
    253 
    254   public static final class NotInterceptable {}
    255 
    256   public void testInterceptingNonBridgeWorks() {
    257     Injector injector = Guice.createInjector(new AbstractModule() {
    258       @Override
    259       protected void configure() {
    260         bind(Interface.class).to(Impl.class);
    261         bindInterceptor(Matchers.any(), new AbstractMatcher<Method>() {
    262           public boolean matches(Method t) {
    263             return !t.isBridge() && t.getDeclaringClass() != Object.class;
    264           }
    265         }, new CountingInterceptor());
    266       }
    267     });
    268     Interface intf = injector.getInstance(Interface.class);
    269     assertEquals(0, count.get());
    270     intf.aMethod(null);
    271     assertEquals(1, count.get());
    272   }
    273 
    274   static class ErasedType {}
    275   static class RetType extends ErasedType {}
    276   static abstract class Superclass<T extends ErasedType> {
    277       public T aMethod(T t) { return null; }
    278   }
    279   public interface Interface {
    280       RetType aMethod(RetType obj);
    281   }
    282   public static class Impl extends Superclass<RetType> implements Interface {
    283   }
    284 
    285   public void testInterceptionOrder() {
    286     final List<String> callList = Lists.newArrayList();
    287     Injector injector = Guice.createInjector(new AbstractModule() {
    288       protected void configure() {
    289         bindInterceptor(Matchers.any(), Matchers.any(),
    290           new NamedInterceptor("a", callList),
    291           new NamedInterceptor("b", callList),
    292           new NamedInterceptor("c", callList));
    293       }
    294     });
    295 
    296     Interceptable interceptable = injector.getInstance(Interceptable.class);
    297     assertEquals(0, callList.size());
    298     interceptable.foo();
    299     assertEquals(Arrays.asList("a", "b", "c"), callList);
    300   }
    301 
    302   private final class NamedInterceptor implements MethodInterceptor {
    303     private final String name;
    304     final List<String> called;
    305 
    306     NamedInterceptor(String name, List<String> callList) {
    307       this.name = name;
    308       this.called = callList;
    309     }
    310 
    311     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    312       called.add(name);
    313       return methodInvocation.proceed();
    314     }
    315   }
    316 
    317   public void testDeDuplicateInterceptors() throws Exception {
    318     Injector injector = Guice.createInjector(new AbstractModule() {
    319       @Override protected void configure() {
    320         CountingInterceptor interceptor = new CountingInterceptor();
    321         bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
    322         bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
    323       }
    324     });
    325 
    326     Interceptable interceptable = injector.getInstance(Interceptable.class);
    327     interceptable.foo();
    328     assertEquals(1, count.get());
    329   }
    330 
    331   public void testCallLater() {
    332     final Queue<Runnable> queue = Lists.newLinkedList();
    333     Injector injector = Guice.createInjector(new AbstractModule() {
    334       protected void configure() {
    335         bindInterceptor(Matchers.any(), Matchers.any(), new CallLaterInterceptor(queue));
    336       }
    337     });
    338 
    339     Interceptable interceptable = injector.getInstance(Interceptable.class);
    340     interceptable.foo();
    341     assertNull(interceptable.lastElements);
    342     assertEquals(1, queue.size());
    343 
    344     queue.remove().run();
    345     assertNotNull(interceptable.lastElements);
    346   }
    347 
    348   private final class CallLaterInterceptor implements MethodInterceptor {
    349     private final Queue<Runnable> queue;
    350 
    351     public CallLaterInterceptor(Queue<Runnable> queue) {
    352       this.queue = queue;
    353     }
    354 
    355     public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
    356       queue.add(new Runnable() {
    357         @Override
    358         public void run() {
    359           try {
    360             methodInvocation.proceed();
    361           } catch (Throwable t) {
    362             throw new RuntimeException(t);
    363           }
    364         }
    365       });
    366       return null;
    367     }
    368   }
    369 }
    370