• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import java.lang.reflect.Method;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Queue;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.concurrent.atomic.AtomicReference;
34 import junit.framework.TestCase;
35 import org.aopalliance.intercept.MethodInterceptor;
36 import org.aopalliance.intercept.MethodInvocation;
37 
38 /** @author jessewilson@google.com (Jesse Wilson) */
39 public class MethodInterceptionTest extends TestCase {
40 
41   private AtomicInteger count = new AtomicInteger();
42 
43   private final class CountingInterceptor implements MethodInterceptor {
44     @Override
invoke(MethodInvocation methodInvocation)45     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
46       count.incrementAndGet();
47       return methodInvocation.proceed();
48     }
49   }
50 
51   private static final class ReturnNullInterceptor implements MethodInterceptor {
52     @Override
invoke(MethodInvocation methodInvocation)53     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
54       return null;
55     }
56   }
57 
58   private static final class NoOpInterceptor implements MethodInterceptor {
59     @Override
invoke(MethodInvocation methodInvocation)60     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
61       return methodInvocation.proceed();
62     }
63   }
64 
testSharedProxyClasses()65   public void testSharedProxyClasses() {
66     Injector injector =
67         Guice.createInjector(
68             new AbstractModule() {
69               @Override
70               protected void configure() {
71                 bindInterceptor(
72                     Matchers.any(), Matchers.returns(only(Foo.class)), new ReturnNullInterceptor());
73               }
74             });
75 
76     Injector childOne =
77         injector.createChildInjector(
78             new AbstractModule() {
79               @Override
80               protected void configure() {
81                 bind(Interceptable.class);
82               }
83             });
84 
85     Interceptable nullFoosOne = childOne.getInstance(Interceptable.class);
86     assertNotNull(nullFoosOne.bar());
87     assertNull(nullFoosOne.foo()); // confirm it's being intercepted
88 
89     Injector childTwo =
90         injector.createChildInjector(
91             new AbstractModule() {
92               @Override
93               protected void configure() {
94                 bind(Interceptable.class);
95               }
96             });
97 
98     Interceptable nullFoosTwo = childTwo.getInstance(Interceptable.class);
99     assertNull(nullFoosTwo.foo()); // confirm it's being intercepted
100 
101     assertSame(
102         "Child injectors should share proxy classes, otherwise memory leaks!",
103         nullFoosOne.getClass(),
104         nullFoosTwo.getClass());
105 
106     Injector injector2 =
107         Guice.createInjector(
108             new AbstractModule() {
109               @Override
110               protected void configure() {
111                 bindInterceptor(
112                     Matchers.any(), Matchers.returns(only(Foo.class)), new ReturnNullInterceptor());
113               }
114             });
115     Interceptable separateNullFoos = injector2.getInstance(Interceptable.class);
116     assertNull(separateNullFoos.foo()); // confirm it's being intercepted
117     assertSame(
118         "different injectors should share proxy classes, otherwise memory leaks!",
119         nullFoosOne.getClass(),
120         separateNullFoos.getClass());
121   }
122 
testGetThis()123   public void testGetThis() {
124     final AtomicReference<Object> lastTarget = new AtomicReference<>();
125 
126     Injector injector =
127         Guice.createInjector(
128             new AbstractModule() {
129               @Override
130               protected void configure() {
131                 bindInterceptor(
132                     Matchers.any(),
133                     Matchers.any(),
134                     new MethodInterceptor() {
135                       @Override
136                       public Object invoke(MethodInvocation methodInvocation) throws Throwable {
137                         lastTarget.set(methodInvocation.getThis());
138                         return methodInvocation.proceed();
139                       }
140                     });
141               }
142             });
143 
144     Interceptable interceptable = injector.getInstance(Interceptable.class);
145     interceptable.foo();
146     assertSame(interceptable, lastTarget.get());
147   }
148 
testInterceptingFinalClass()149   public void testInterceptingFinalClass() {
150     Injector injector =
151         Guice.createInjector(
152             new AbstractModule() {
153               @Override
154               protected void configure() {
155                 bindInterceptor(
156                     Matchers.any(),
157                     Matchers.any(),
158                     new MethodInterceptor() {
159                       @Override
160                       public Object invoke(MethodInvocation methodInvocation) throws Throwable {
161                         return methodInvocation.proceed();
162                       }
163                     });
164               }
165             });
166     try {
167       injector.getInstance(NotInterceptable.class);
168       fail();
169     } catch (ConfigurationException ce) {
170       assertEquals(
171           "Unable to method intercept: " + NotInterceptable.class.getName(),
172           Iterables.getOnlyElement(ce.getErrorMessages()).getMessage().toString());
173       assertEquals(
174           "Cannot subclass final class " + NotInterceptable.class.getName(),
175           ce.getCause().getMessage());
176     }
177   }
178 
testSpiAccessToInterceptors()179   public void testSpiAccessToInterceptors() throws NoSuchMethodException {
180     final MethodInterceptor countingInterceptor = new CountingInterceptor();
181     final MethodInterceptor returnNullInterceptor = new ReturnNullInterceptor();
182     Injector injector =
183         Guice.createInjector(
184             new AbstractModule() {
185               @Override
186               protected void configure() {
187                 bindInterceptor(
188                     Matchers.any(), Matchers.returns(only(Foo.class)), countingInterceptor);
189                 bindInterceptor(
190                     Matchers.any(),
191                     Matchers.returns(only(Foo.class).or(only(Bar.class))),
192                     returnNullInterceptor);
193               }
194             });
195 
196     ConstructorBinding<?> interceptedBinding =
197         (ConstructorBinding<?>) injector.getBinding(Interceptable.class);
198     Method barMethod = Interceptable.class.getMethod("bar");
199     Method fooMethod = Interceptable.class.getMethod("foo");
200     assertEquals(
201         ImmutableMap.<Method, List<MethodInterceptor>>of(
202             fooMethod, ImmutableList.of(countingInterceptor, returnNullInterceptor),
203             barMethod, ImmutableList.of(returnNullInterceptor)),
204         interceptedBinding.getMethodInterceptors());
205 
206     ConstructorBinding<?> nonInterceptedBinding =
207         (ConstructorBinding<?>) injector.getBinding(Foo.class);
208     assertEquals(
209         ImmutableMap.<Method, List<MethodInterceptor>>of(),
210         nonInterceptedBinding.getMethodInterceptors());
211 
212     injector.getInstance(Interceptable.class).foo();
213     assertEquals("expected counting interceptor to be invoked first", 1, count.get());
214   }
215 
testInterceptedMethodThrows()216   public void testInterceptedMethodThrows() throws Exception {
217     Injector injector =
218         Guice.createInjector(
219             new AbstractModule() {
220               @Override
221               protected void configure() {
222                 bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
223                 bindInterceptor(Matchers.any(), Matchers.any(), new CountingInterceptor());
224               }
225             });
226 
227     Interceptable interceptable = injector.getInstance(Interceptable.class);
228     try {
229       interceptable.explode();
230       fail();
231     } catch (Exception e) {
232       // validate all causes.
233       for (Throwable t = e; t != null; t = t.getCause()) {
234         StackTraceElement[] stackTraceElement = t.getStackTrace();
235         assertEquals("explode", stackTraceElement[0].getMethodName());
236         assertEquals("invoke", stackTraceElement[1].getMethodName());
237         assertEquals("invoke", stackTraceElement[2].getMethodName());
238         assertEquals("testInterceptedMethodThrows", stackTraceElement[3].getMethodName());
239       }
240     }
241   }
242 
testNotInterceptedMethodsInInterceptedClassDontAddFrames()243   public void testNotInterceptedMethodsInInterceptedClassDontAddFrames() {
244     Injector injector =
245         Guice.createInjector(
246             new AbstractModule() {
247               @Override
248               protected void configure() {
249                 bindInterceptor(
250                     Matchers.any(), Matchers.returns(only(Foo.class)), new NoOpInterceptor());
251               }
252             });
253 
254     Interceptable interceptable = injector.getInstance(Interceptable.class);
255     assertNull(interceptable.lastElements);
256     interceptable.foo();
257     boolean cglibFound = false;
258     for (int i = 0; i < interceptable.lastElements.length; i++) {
259       if (interceptable.lastElements[i].toString().contains("cglib")) {
260         cglibFound = true;
261         break;
262       }
263     }
264     assertTrue(Arrays.toString(interceptable.lastElements), cglibFound);
265     cglibFound = false;
266 
267     interceptable.bar();
268     for (int i = 0; i < interceptable.lastElements.length; i++) {
269       if (interceptable.lastElements[i].toString().contains("cglib")) {
270         cglibFound = true;
271         break;
272       }
273     }
274     assertFalse(Arrays.toString(interceptable.lastElements), cglibFound);
275   }
276 
277   static class Foo {}
278 
279   static class Bar {}
280 
281   public static class Interceptable {
282     StackTraceElement[] lastElements;
283 
foo()284     public Foo foo() {
285       lastElements = Thread.currentThread().getStackTrace();
286       return new Foo() {};
287     }
288 
bar()289     public Bar bar() {
290       lastElements = Thread.currentThread().getStackTrace();
291       return new Bar() {};
292     }
293 
explode()294     public String explode() throws Exception {
295       lastElements = Thread.currentThread().getStackTrace();
296       throw new Exception("kaboom!", new RuntimeException("boom!"));
297     }
298   }
299 
300   public static final class NotInterceptable {}
301 
testInterceptingNonBridgeWorks()302   public void testInterceptingNonBridgeWorks() {
303     Injector injector =
304         Guice.createInjector(
305             new AbstractModule() {
306               @Override
307               protected void configure() {
308                 bind(Interface.class).to(Impl.class);
309                 bindInterceptor(
310                     Matchers.any(),
311                     new AbstractMatcher<Method>() {
312                       @Override
313                       public boolean matches(Method t) {
314                         return !t.isBridge() && t.getDeclaringClass() != Object.class;
315                       }
316                     },
317                     new CountingInterceptor());
318               }
319             });
320     Interface intf = injector.getInstance(Interface.class);
321     assertEquals(0, count.get());
322     intf.aMethod(null);
323     assertEquals(1, count.get());
324   }
325 
326   static class ErasedType {}
327 
328   static class RetType extends ErasedType {}
329 
330   abstract static class Superclass<T extends ErasedType> {
aMethod(T t)331     public T aMethod(T t) {
332       return null;
333     }
334   }
335 
336   public interface Interface {
aMethod(RetType obj)337     RetType aMethod(RetType obj);
338   }
339 
340   public static class Impl extends Superclass<RetType> implements Interface {}
341 
testInterceptionOrder()342   public void testInterceptionOrder() {
343     final List<String> callList = Lists.newArrayList();
344     Injector injector =
345         Guice.createInjector(
346             new AbstractModule() {
347               @Override
348               protected void configure() {
349                 bindInterceptor(
350                     Matchers.any(),
351                     Matchers.any(),
352                     new NamedInterceptor("a", callList),
353                     new NamedInterceptor("b", callList),
354                     new NamedInterceptor("c", callList));
355               }
356             });
357 
358     Interceptable interceptable = injector.getInstance(Interceptable.class);
359     assertEquals(0, callList.size());
360     interceptable.foo();
361     assertEquals(Arrays.asList("a", "b", "c"), callList);
362   }
363 
364   private static final class NamedInterceptor implements MethodInterceptor {
365     private final String name;
366     final List<String> called;
367 
NamedInterceptor(String name, List<String> callList)368     NamedInterceptor(String name, List<String> callList) {
369       this.name = name;
370       this.called = callList;
371     }
372 
373     @Override
invoke(MethodInvocation methodInvocation)374     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
375       called.add(name);
376       return methodInvocation.proceed();
377     }
378   }
379 
testDeDuplicateInterceptors()380   public void testDeDuplicateInterceptors() throws Exception {
381     Injector injector =
382         Guice.createInjector(
383             new AbstractModule() {
384               @Override
385               protected void configure() {
386                 CountingInterceptor interceptor = new CountingInterceptor();
387                 bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
388                 bindInterceptor(Matchers.any(), Matchers.any(), interceptor);
389               }
390             });
391 
392     Interceptable interceptable = injector.getInstance(Interceptable.class);
393     interceptable.foo();
394     assertEquals(1, count.get());
395   }
396 
testCallLater()397   public void testCallLater() {
398     final Queue<Runnable> queue = Lists.newLinkedList();
399     Injector injector =
400         Guice.createInjector(
401             new AbstractModule() {
402               @Override
403               protected void configure() {
404                 bindInterceptor(Matchers.any(), Matchers.any(), new CallLaterInterceptor(queue));
405               }
406             });
407 
408     Interceptable interceptable = injector.getInstance(Interceptable.class);
409     interceptable.foo();
410     assertNull(interceptable.lastElements);
411     assertEquals(1, queue.size());
412 
413     queue.remove().run();
414     assertNotNull(interceptable.lastElements);
415   }
416 
417   private static final class CallLaterInterceptor implements MethodInterceptor {
418     private final Queue<Runnable> queue;
419 
CallLaterInterceptor(Queue<Runnable> queue)420     public CallLaterInterceptor(Queue<Runnable> queue) {
421       this.queue = queue;
422     }
423 
424     @Override
invoke(final MethodInvocation methodInvocation)425     public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
426       queue.add(
427           new Runnable() {
428             @Override
429             public void run() {
430               try {
431                 methodInvocation.proceed();
432               } catch (Throwable t) {
433                 throw new RuntimeException(t);
434               }
435             }
436           });
437       return null;
438     }
439   }
440 }
441