001// Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation 002// 003// Licensed under the Apache License, Version 2.0 (the "License"); 004// you may not use this file except in compliance with the License. 005// You may obtain a copy of the License at 006// 007// http://www.apache.org/licenses/LICENSE-2.0 008// 009// Unless required by applicable law or agreed to in writing, software 010// distributed under the License is distributed on an "AS IS" BASIS, 011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012// See the License for the specific language governing permissions and 013// limitations under the License. 014 015package org.apache.tapestry5.ioc.internal; 016 017import org.apache.tapestry5.commons.*; 018import org.apache.tapestry5.commons.internal.util.*; 019import org.apache.tapestry5.commons.services.PlasticProxyFactory; 020import org.apache.tapestry5.commons.services.TypeCoercer; 021import org.apache.tapestry5.commons.util.CollectionFactory; 022import org.apache.tapestry5.ioc.AdvisorDef; 023import org.apache.tapestry5.ioc.Invokable; 024import org.apache.tapestry5.ioc.Markable; 025import org.apache.tapestry5.ioc.OperationTracker; 026import org.apache.tapestry5.ioc.ServiceBuilderResources; 027import org.apache.tapestry5.ioc.ServiceLifecycle2; 028import org.apache.tapestry5.ioc.ServiceResources; 029import org.apache.tapestry5.ioc.annotations.Local; 030import org.apache.tapestry5.ioc.def.*; 031import org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator; 032import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier; 033import org.apache.tapestry5.ioc.internal.util.InjectionResources; 034import org.apache.tapestry5.ioc.internal.util.InternalUtils; 035import org.apache.tapestry5.ioc.internal.util.MapInjectionResources; 036import org.apache.tapestry5.ioc.services.AspectDecorator; 037import org.apache.tapestry5.ioc.services.Status; 038import org.apache.tapestry5.plastic.*; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import java.io.ObjectStreamException; 043import java.io.Serializable; 044import java.lang.invoke.MethodHandles; 045import java.lang.invoke.MethodType; 046import java.lang.reflect.Constructor; 047import java.lang.reflect.InvocationTargetException; 048import java.lang.reflect.Method; 049import java.lang.reflect.Modifier; 050import java.util.*; 051import java.util.function.Predicate; 052 053import static java.lang.String.format; 054 055@SuppressWarnings("all") 056public class ModuleImpl implements Module 057{ 058 private final InternalRegistry registry; 059 060 private final ServiceActivityTracker tracker; 061 062 private final ModuleDef2 moduleDef; 063 064 private final PlasticProxyFactory proxyFactory; 065 066 private final Logger logger; 067 068 private static final Predicate<Class> canBeProxiedPredicate; 069 070 static 071 { 072 Predicate<Class> predicate = null; 073 try { 074 final MethodHandles.Lookup lookup = MethodHandles.lookup(); 075 final MethodType methodType = MethodType.methodType(boolean.class); 076 final java.lang.invoke.MethodHandle isSealedMethodHandle = 077 MethodHandles.lookup().findVirtual(Class.class, "isSealed", methodType); 078 predicate = c -> c.isInterface() && !callIsSealed(isSealedMethodHandle, c); 079 } 080 catch (NoSuchMethodException e) 081 { 082 LoggerFactory.getLogger(ModuleImpl.class) 083 .info("Method Class.isSealed() not found, so we're running in a pre-15 JVM."); 084 } 085 catch (IllegalAccessException e) 086 { 087 throw new RuntimeException(e); 088 } 089 090 canBeProxiedPredicate = predicate != null ? predicate : c -> c.isInterface(); 091 092 } 093 094 private static boolean callIsSealed(final java.lang.invoke.MethodHandle isSealedMethodHandle, Class clasz) 095 { 096 try 097 { 098 return (Boolean) isSealedMethodHandle.invoke(clasz); 099 } catch (Throwable e) { 100 throw new RuntimeException(e); 101 } 102 } 103 104 /** 105 * Lazily instantiated. Access is guarded by BARRIER. 106 */ 107 private Object moduleInstance; 108 109 // Set to true when invoking the module constructor. Used to 110 // detect endless loops caused by irresponsible dependencies in 111 // the constructor. 112 private boolean insideConstructor; 113 114 /** 115 * Keyed on fully qualified service id; values are instantiated services (proxies). Guarded by BARRIER. 116 */ 117 private final Map<String, Object> services = CollectionFactory.newCaseInsensitiveMap(); 118 119 private final Map<String, ServiceDef3> serviceDefs = CollectionFactory.newCaseInsensitiveMap(); 120 121 /** 122 * The barrier is shared by all modules, which means that creation of *any* service for any module is single 123 * threaded. 124 */ 125 private final static ConcurrentBarrier BARRIER = new ConcurrentBarrier(); 126 127 /** 128 * "Magic" method related to Serializable that allows the Proxy object to replace itself with the token when being 129 * streamed out. 130 */ 131 private static final MethodDescription WRITE_REPLACE = new MethodDescription(Modifier.PRIVATE, "java.lang.Object", 132 "writeReplace", null, null, new String[] 133 {ObjectStreamException.class.getName()}); 134 135 public ModuleImpl(InternalRegistry registry, ServiceActivityTracker tracker, ModuleDef moduleDef, 136 PlasticProxyFactory proxyFactory, Logger logger) 137 { 138 this.registry = registry; 139 this.tracker = tracker; 140 this.proxyFactory = proxyFactory; 141 this.moduleDef = InternalUtils.toModuleDef2(moduleDef); 142 this.logger = logger; 143 144 for (String id : moduleDef.getServiceIds()) 145 { 146 ServiceDef sd = moduleDef.getServiceDef(id); 147 148 ServiceDef3 sd3 = InternalUtils.toServiceDef3(sd); 149 150 serviceDefs.put(id, sd3); 151 } 152 } 153 154 @Override 155 public <T> T getService(String serviceId, Class<T> serviceInterface) 156 { 157 assert InternalUtils.isNonBlank(serviceId); 158 assert serviceInterface != null; 159 ServiceDef3 def = getServiceDef(serviceId); 160 161 // RegistryImpl should already have checked that the service exists. 162 assert def != null; 163 164 Object service = findOrCreate(def, null); 165 166 try 167 { 168 return serviceInterface.cast(service); 169 } catch (ClassCastException ex) 170 { 171 // This may be overkill: I don't know how this could happen 172 // given that the return type of the method determines 173 // the service interface. 174 175 throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, def.getServiceInterface(), 176 serviceInterface)); 177 } 178 } 179 180 @Override 181 public Set<DecoratorDef> findMatchingDecoratorDefs(ServiceDef serviceDef) 182 { 183 Set<DecoratorDef> result = CollectionFactory.newSet(); 184 185 for (DecoratorDef def : moduleDef.getDecoratorDefs()) 186 { 187 if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toDecoratorDef2(def))) 188 result.add(def); 189 } 190 191 return result; 192 } 193 194 @Override 195 public Set<AdvisorDef> findMatchingServiceAdvisors(ServiceDef serviceDef) 196 { 197 Set<AdvisorDef> result = CollectionFactory.newSet(); 198 199 for (AdvisorDef def : moduleDef.getAdvisorDefs()) 200 { 201 if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toAdvisorDef2(def))) 202 result.add(def); 203 } 204 205 return result; 206 } 207 208 @Override 209 @SuppressWarnings("unchecked") 210 public Collection<String> findServiceIdsForInterface(Class serviceInterface) 211 { 212 assert serviceInterface != null; 213 Collection<String> result = CollectionFactory.newList(); 214 215 for (ServiceDef2 def : serviceDefs.values()) 216 { 217 if (serviceInterface.isAssignableFrom(def.getServiceInterface())) 218 result.add(def.getServiceId()); 219 } 220 221 return result; 222 } 223 224 /** 225 * Locates the service proxy for a particular service (from the service definition). 226 * 227 * @param def defines the service 228 * @param eagerLoadProxies collection into which proxies for eager loaded services are added (or null) 229 * @return the service proxy 230 */ 231 private Object findOrCreate(final ServiceDef3 def, final Collection<EagerLoadServiceProxy> eagerLoadProxies) 232 { 233 final String key = def.getServiceId(); 234 235 final Invokable create = new Invokable() 236 { 237 @Override 238 public Object invoke() 239 { 240 // In a race condition, two threads may try to create the same service simulatenously. 241 // The second will block until after the first creates the service. 242 243 Object result = services.get(key); 244 245 // Normally, result is null, unless some other thread slipped in and created the service 246 // proxy. 247 248 if (result == null) 249 { 250 result = create(def, eagerLoadProxies); 251 252 services.put(key, result); 253 } 254 255 return result; 256 } 257 }; 258 259 Invokable find = new Invokable() 260 { 261 @Override 262 public Object invoke() 263 { 264 Object result = services.get(key); 265 266 if (result == null) 267 result = BARRIER.withWrite(create); 268 269 return result; 270 } 271 }; 272 273 return BARRIER.withRead(find); 274 } 275 276 @Override 277 public void collectEagerLoadServices(final Collection<EagerLoadServiceProxy> proxies) 278 { 279 Runnable work = new Runnable() 280 { 281 @Override 282 public void run() 283 { 284 for (ServiceDef3 def : serviceDefs.values()) 285 { 286 if (def.isEagerLoad()) 287 findOrCreate(def, proxies); 288 } 289 } 290 }; 291 292 registry.run("Eager loading services", work); 293 } 294 295 /** 296 * Creates the service and updates the cache of created services. 297 * 298 * @param eagerLoadProxies a list into which any eager loaded proxies should be added 299 */ 300 private Object create(final ServiceDef3 def, final Collection<EagerLoadServiceProxy> eagerLoadProxies) 301 { 302 final String serviceId = def.getServiceId(); 303 304 final Logger logger = registry.getServiceLogger(serviceId); 305 306 final Class serviceInterface = def.getServiceInterface(); 307 308 final boolean canBeProxied = canBeProxiedPredicate.test(serviceInterface); 309 String description = String.format("Creating %s service %s", 310 canBeProxied ? "proxy for" : "non-proxied instance of", 311 serviceId); 312 313 if (logger.isDebugEnabled()) 314 logger.debug(description); 315 316 final Module module = this; 317 318 Invokable operation = new Invokable() 319 { 320 @Override 321 public Object invoke() 322 { 323 try 324 { 325 ServiceBuilderResources resources = new ServiceResourcesImpl(registry, module, def, proxyFactory, 326 logger); 327 328 // Build up a stack of operations that will be needed to realize the service 329 // (by the proxy, at a later date). 330 331 ObjectCreator creator = def.createServiceCreator(resources); 332 333 334 // For non-proxyable services, we immediately create the service implementation 335 // and return it. There's no interface to proxy, which throws out the possibility of 336 // deferred instantiation, service lifecycles, and decorators. 337 338 ServiceLifecycle2 lifecycle = registry.getServiceLifecycle(def.getServiceScope()); 339 340 if (!canBeProxied) 341 { 342 if (lifecycle.requiresProxy()) 343 throw new IllegalArgumentException( 344 String.format( 345 "Service scope '%s' requires a proxy, but the service does not have a service interface (necessary to create a proxy). Provide a service interface or select a different service scope.", 346 def.getServiceScope())); 347 348 return creator.createObject(); 349 } 350 351 creator = new OperationTrackingObjectCreator(registry, String.format("Instantiating service %s implementation via %s", serviceId, creator), creator); 352 353 creator = new LifecycleWrappedServiceCreator(lifecycle, resources, creator); 354 355 // Marked services (or services inside marked modules) are not decorated. 356 // TapestryIOCModule prevents decoration of its services. Note that all decorators will decorate 357 // around the aspect interceptor, which wraps around the core service implementation. 358 359 boolean allowDecoration = !def.isPreventDecoration(); 360 361 if (allowDecoration) 362 { 363 creator = new AdvisorStackBuilder(def, creator, getAspectDecorator(), registry); 364 creator = new InterceptorStackBuilder(def, creator, registry); 365 } 366 367 // Add a wrapper that checks for recursion. 368 369 creator = new RecursiveServiceCreationCheckWrapper(def, creator, logger); 370 371 creator = new OperationTrackingObjectCreator(registry, "Realizing service " + serviceId, creator); 372 373 JustInTimeObjectCreator delegate = new JustInTimeObjectCreator(tracker, creator, serviceId); 374 375 Object proxy = createProxy(resources, delegate, def.isPreventDecoration()); 376 377 registry.addRegistryShutdownListener(delegate); 378 379 // Occasionally eager load service A may invoke service B from its service builder method; if 380 // service B is eager loaded, we'll hit this method but eagerLoadProxies will be null. That's OK 381 // ... service B is being realized anyway. 382 383 if (def.isEagerLoad() && eagerLoadProxies != null) 384 eagerLoadProxies.add(delegate); 385 386 tracker.setStatus(serviceId, Status.VIRTUAL); 387 388 return proxy; 389 } catch (Exception ex) 390 { 391 ex.printStackTrace(); 392 throw new RuntimeException(IOCMessages.errorBuildingService(serviceId, def, ex), ex); 393 } 394 } 395 }; 396 397 return registry.invoke(description, operation); 398 } 399 400 private AspectDecorator getAspectDecorator() 401 { 402 return registry.invoke("Obtaining AspectDecorator service", new Invokable<AspectDecorator>() 403 { 404 @Override 405 public AspectDecorator invoke() 406 { 407 return registry.getService(AspectDecorator.class); 408 } 409 }); 410 } 411 412 private final Runnable instantiateModule = new Runnable() 413 { 414 @Override 415 public void run() 416 { 417 moduleInstance = registry.invoke("Constructing module class " + moduleDef.getBuilderClass().getName(), 418 new Invokable() 419 { 420 @Override 421 public Object invoke() 422 { 423 return instantiateModuleInstance(); 424 } 425 }); 426 } 427 }; 428 429 private final Invokable provideModuleInstance = new Invokable<Object>() 430 { 431 @Override 432 public Object invoke() 433 { 434 if (moduleInstance == null) 435 BARRIER.withWrite(instantiateModule); 436 437 return moduleInstance; 438 } 439 }; 440 441 @Override 442 public Object getModuleBuilder() 443 { 444 return BARRIER.withRead(provideModuleInstance); 445 } 446 447 private Object instantiateModuleInstance() 448 { 449 Class moduleClass = moduleDef.getBuilderClass(); 450 451 Constructor[] constructors = moduleClass.getConstructors(); 452 453 if (constructors.length == 0) 454 throw new RuntimeException(IOCMessages.noPublicConstructors(moduleClass)); 455 456 if (constructors.length > 1) 457 { 458 // Sort the constructors ascending by number of parameters (descending); this is really 459 // just to allow the test suite to work properly across different JVMs (which will 460 // often order the constructors differently). 461 462 Comparator<Constructor> comparator = new Comparator<Constructor>() 463 { 464 @Override 465 public int compare(Constructor c1, Constructor c2) 466 { 467 return c2.getParameterTypes().length - c1.getParameterTypes().length; 468 } 469 }; 470 471 Arrays.sort(constructors, comparator); 472 473 logger.warn(IOCMessages.tooManyPublicConstructors(moduleClass, constructors[0])); 474 } 475 476 Constructor constructor = constructors[0]; 477 478 if (insideConstructor) 479 throw new RuntimeException(IOCMessages.recursiveModuleConstructor(moduleClass, constructor)); 480 481 ObjectLocator locator = new ObjectLocatorImpl(registry, this); 482 Map<Class, Object> resourcesMap = CollectionFactory.newMap(); 483 484 resourcesMap.put(Logger.class, logger); 485 resourcesMap.put(ObjectLocator.class, locator); 486 resourcesMap.put(OperationTracker.class, registry); 487 488 InjectionResources resources = new MapInjectionResources(resourcesMap); 489 490 Throwable fail = null; 491 492 try 493 { 494 insideConstructor = true; 495 496 ObjectCreator[] parameterValues = InternalUtils.calculateParameters(locator, resources, 497 constructor.getParameterTypes(), constructor.getGenericParameterTypes(), 498 constructor.getParameterAnnotations(), registry); 499 500 Object[] realized = InternalUtils.realizeObjects(parameterValues); 501 502 Object result = constructor.newInstance(realized); 503 504 InternalUtils.injectIntoFields(result, locator, resources, registry); 505 506 return result; 507 } catch (InvocationTargetException ex) 508 { 509 fail = ex.getTargetException(); 510 } catch (Exception ex) 511 { 512 fail = ex; 513 } finally 514 { 515 insideConstructor = false; 516 } 517 518 throw new RuntimeException(IOCMessages.instantiateBuilderError(moduleClass, fail), fail); 519 } 520 521 private Object createProxy(ServiceResources resources, ObjectCreator creator, boolean preventDecoration) 522 { 523 String serviceId = resources.getServiceId(); 524 Class serviceInterface = resources.getServiceInterface(); 525 526 String toString = format("<Proxy for %s(%s)>", serviceId, serviceInterface.getName()); 527 528 ServiceProxyToken token = SerializationSupport.createToken(serviceId); 529 530 final Class serviceImplementation = preventDecoration || serviceInterface == TypeCoercer.class ? null : resources.getServiceImplementation(); 531 return createProxyInstance(creator, token, serviceInterface, serviceImplementation, toString); 532 } 533 534 private Object createProxyInstance(final ObjectCreator creator, final ServiceProxyToken token, 535 final Class serviceInterface, final Class serviceImplementation, final String description) 536 { 537 ClassInstantiator instantiator = proxyFactory.createProxy(serviceInterface, serviceImplementation, new PlasticClassTransformer() 538 { 539 @Override 540 public void transform(final PlasticClass plasticClass) 541 { 542 plasticClass.introduceInterface(Serializable.class); 543 544 final PlasticField creatorField = plasticClass.introduceField(ObjectCreator.class, "creator").inject( 545 creator); 546 547 final PlasticField tokenField = plasticClass.introduceField(ServiceProxyToken.class, "token").inject( 548 token); 549 550 PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(serviceInterface.getName(), 551 "delegate", null, null); 552 553 // If not concerned with efficiency, this might be done with method advice instead. 554 delegateMethod.changeImplementation(new InstructionBuilderCallback() 555 { 556 @Override 557 public void doBuild(InstructionBuilder builder) 558 { 559 builder.loadThis().getField(creatorField); 560 builder.invoke(ObjectCreator.class, Object.class, "createObject").checkcast(serviceInterface) 561 .returnResult(); 562 } 563 }); 564 565 plasticClass.proxyInterface(serviceInterface, delegateMethod); 566 567 plasticClass.introduceMethod(WRITE_REPLACE).changeImplementation(new InstructionBuilderCallback() 568 { 569 @Override 570 public void doBuild(InstructionBuilder builder) 571 { 572 builder.loadThis().getField(tokenField).returnResult(); 573 } 574 }); 575 576 plasticClass.addToString(description); 577 } 578 }, false); 579 580 return instantiator.newInstance(); 581 } 582 583 @Override 584 @SuppressWarnings("all") 585 public Set<ContributionDef2> getContributorDefsForService(ServiceDef serviceDef) 586 { 587 Set<ContributionDef2> result = CollectionFactory.newSet(); 588 589 for (ContributionDef next : moduleDef.getContributionDefs()) 590 { 591 ContributionDef2 def = InternalUtils.toContributionDef2(next); 592 593 if (serviceDef.getServiceId().equalsIgnoreCase(def.getServiceId())) 594 { 595 result.add(def); 596 } else 597 { 598 if (markerMatched(serviceDef, def)) 599 { 600 result.add(def); 601 } 602 } 603 } 604 605 return result; 606 } 607 608 private boolean markerMatched(ServiceDef serviceDef, Markable markable) 609 { 610 final Class markableInterface = markable.getServiceInterface(); 611 612 if (markableInterface == null || !markableInterface.isAssignableFrom(serviceDef.getServiceInterface())) 613 return false; 614 615 Set<Class> contributionMarkers = CollectionFactory.newSet(markable.getMarkers()); 616 617 if (contributionMarkers.contains(Local.class)) 618 { 619 // If @Local is present, filter out services that aren't in the same module. 620 // Don't consider @Local to be a marker annotation 621 // for the later match, however. 622 623 if (!isLocalServiceDef(serviceDef)) 624 return false; 625 626 contributionMarkers.remove(Local.class); 627 } 628 629 // Filter out any stray annotations that aren't used by some 630 // service, in any module, as a marker annotation. 631 632 contributionMarkers.retainAll(registry.getMarkerAnnotations()); 633 634 //@Advise and @Decorate default to Object.class service interface. 635 //If @Match is present, no marker annotations are needed. 636 //In such a case an empty contribution marker list should be ignored. 637 if (markableInterface == Object.class && contributionMarkers.isEmpty()) 638 return false; 639 640 return serviceDef.getMarkers().containsAll(contributionMarkers); 641 } 642 643 private boolean isLocalServiceDef(ServiceDef serviceDef) 644 { 645 return serviceDefs.containsKey(serviceDef.getServiceId()); 646 } 647 648 @Override 649 public ServiceDef3 getServiceDef(String serviceId) 650 { 651 return serviceDefs.get(serviceId); 652 } 653 654 @Override 655 public String getLoggerName() 656 { 657 return moduleDef.getLoggerName(); 658 } 659 660 @Override 661 public String toString() 662 { 663 return String.format("ModuleImpl[%s]", moduleDef.getLoggerName()); 664 } 665 666}