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