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