PageRenderTime 44ms CodeModel.GetById 29ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/core/test/com/google/inject/MethodInterceptionTest.java

https://gitlab.com/metamorphiccode/guice
Java | 369 lines | 300 code | 50 blank | 19 comment | 8 complexity | 3775ef0913292979a29b2e95030e917b MD5 | raw file
  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
 17package com.google.inject;
 18
 19import static com.google.inject.matcher.Matchers.only;
 20
 21import com.google.common.collect.ImmutableList;
 22import com.google.common.collect.ImmutableMap;
 23import com.google.common.collect.Iterables;
 24import com.google.common.collect.Lists;
 25import com.google.inject.matcher.AbstractMatcher;
 26import com.google.inject.matcher.Matchers;
 27import com.google.inject.spi.ConstructorBinding;
 28
 29import junit.framework.TestCase;
 30
 31import org.aopalliance.intercept.MethodInterceptor;
 32import org.aopalliance.intercept.MethodInvocation;
 33
 34import java.lang.reflect.Method;
 35import java.util.Arrays;
 36import java.util.List;
 37import java.util.Queue;
 38import java.util.concurrent.atomic.AtomicInteger;
 39import java.util.concurrent.atomic.AtomicReference;
 40
 41/**
 42 * @author jessewilson@google.com (Jesse Wilson)
 43 */
 44public 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 " + 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}