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 120 /** 121 * EagerLoadProxies collection into which proxies for eager loaded services are added. Guarded by BARRIER 122 */ 123 private final Collection<EagerLoadServiceProxy> eagerLoadProxies = CollectionFactory.newList(); 124 125 private final Map<String, ServiceDef3> serviceDefs = CollectionFactory.newCaseInsensitiveMap(); 126 127 /** 128 * The barrier is shared by all modules, which means that creation of *any* service for any module is single 129 * threaded. 130 */ 131 private final static ConcurrentBarrier BARRIER = new ConcurrentBarrier(); 132 133 /** 134 * "Magic" method related to Serializable that allows the Proxy object to replace itself with the token when being 135 * streamed out. 136 */ 137 private static final MethodDescription WRITE_REPLACE = new MethodDescription(Modifier.PRIVATE, "java.lang.Object", 138 "writeReplace", null, null, new String[] 139 {ObjectStreamException.class.getName()}); 140 141 public ModuleImpl(InternalRegistry registry, ServiceActivityTracker tracker, ModuleDef moduleDef, 142 PlasticProxyFactory proxyFactory, Logger logger) 143 { 144 this.registry = registry; 145 this.tracker = tracker; 146 this.proxyFactory = proxyFactory; 147 this.moduleDef = InternalUtils.toModuleDef2(moduleDef); 148 this.logger = logger; 149 150 for (String id : moduleDef.getServiceIds()) 151 { 152 ServiceDef sd = moduleDef.getServiceDef(id); 153 154 ServiceDef3 sd3 = InternalUtils.toServiceDef3(sd); 155 156 serviceDefs.put(id, sd3); 157 } 158 } 159 160 @Override 161 public <T> T getService(String serviceId, Class<T> serviceInterface) 162 { 163 assert InternalUtils.isNonBlank(serviceId); 164 assert serviceInterface != null; 165 ServiceDef3 def = getServiceDef(serviceId); 166 167 // RegistryImpl should already have checked that the service exists. 168 assert def != null; 169 170 Object service = findOrCreate(def); 171 172 try 173 { 174 return serviceInterface.cast(service); 175 } catch (ClassCastException ex) 176 { 177 // This may be overkill: I don't know how this could happen 178 // given that the return type of the method determines 179 // the service interface. 180 181 throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, def.getServiceInterface(), 182 serviceInterface)); 183 } 184 } 185 186 @Override 187 public Set<DecoratorDef> findMatchingDecoratorDefs(ServiceDef serviceDef) 188 { 189 Set<DecoratorDef> result = CollectionFactory.newSet(); 190 191 for (DecoratorDef def : moduleDef.getDecoratorDefs()) 192 { 193 if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toDecoratorDef2(def))) 194 result.add(def); 195 } 196 197 return result; 198 } 199 200 @Override 201 public Set<AdvisorDef> findMatchingServiceAdvisors(ServiceDef serviceDef) 202 { 203 Set<AdvisorDef> result = CollectionFactory.newSet(); 204 205 for (AdvisorDef def : moduleDef.getAdvisorDefs()) 206 { 207 if (def.matches(serviceDef) || markerMatched(serviceDef, InternalUtils.toAdvisorDef2(def))) 208 result.add(def); 209 } 210 211 return result; 212 } 213 214 @Override 215 @SuppressWarnings("unchecked") 216 public Collection<String> findServiceIdsForInterface(Class serviceInterface) 217 { 218 assert serviceInterface != null; 219 Collection<String> result = CollectionFactory.newList(); 220 221 for (ServiceDef2 def : serviceDefs.values()) 222 { 223 if (serviceInterface.isAssignableFrom(def.getServiceInterface())) 224 result.add(def.getServiceId()); 225 } 226 227 return result; 228 } 229 230 /** 231 * Locates the service proxy for a particular service (from the service definition). 232 * 233 * @param def defines the service 234 * @return the service proxy 235 */ 236 private Object findOrCreate(final ServiceDef3 def) 237 { 238 final String key = def.getServiceId(); 239 240 final Invokable create = new Invokable() 241 { 242 @Override 243 public Object invoke() 244 { 245 // In a race condition, two threads may try to create the same service simulatenously. 246 // The second will block until after the first creates the service. 247 248 Object result = services.get(key); 249 250 // Normally, result is null, unless some other thread slipped in and created the service 251 // proxy. 252 253 if (result == null) 254 { 255 result = create(def); 256 257 services.put(key, result); 258 } 259 260 return result; 261 } 262 }; 263 264 Invokable find = new Invokable() 265 { 266 @Override 267 public Object invoke() 268 { 269 Object result = services.get(key); 270 271 if (result == null) 272 result = BARRIER.withWrite(create); 273 274 return result; 275 } 276 }; 277 278 return BARRIER.withRead(find); 279 } 280 281 @Override 282 public void collectEagerLoadServices(final Collection<EagerLoadServiceProxy> proxies) 283 { 284 Runnable work = new Runnable() 285 { 286 @Override 287 public void run() 288 { 289 for (ServiceDef3 def : serviceDefs.values()) 290 { 291 if (def.isEagerLoad()) 292 findOrCreate(def); 293 } 294 295 BARRIER.withWrite(new Runnable() { 296 @Override 297 public void run() { 298 proxies.addAll(eagerLoadProxies); 299 eagerLoadProxies.clear(); 300 } 301 }); 302 } 303 }; 304 305 registry.run("Eager loading services", work); 306 } 307 308 /** 309 * Creates the service and updates the cache of created services. 310 */ 311 private Object create(final ServiceDef3 def) 312 { 313 final String serviceId = def.getServiceId(); 314 315 final Logger logger = registry.getServiceLogger(serviceId); 316 317 final Class serviceInterface = def.getServiceInterface(); 318 319 final boolean canBeProxied = canBeProxiedPredicate.test(serviceInterface); 320 String description = String.format("Creating %s service %s", 321 canBeProxied ? "proxy for" : "non-proxied instance of", 322 serviceId); 323 324 if (logger.isDebugEnabled()) 325 logger.debug(description); 326 327 final Module module = this; 328 329 Invokable operation = new Invokable() 330 { 331 @Override 332 public Object invoke() 333 { 334 try 335 { 336 ServiceBuilderResources resources = new ServiceResourcesImpl(registry, module, def, proxyFactory, 337 logger); 338 339 // Build up a stack of operations that will be needed to realize the service 340 // (by the proxy, at a later date). 341 342 ObjectCreator creator = def.createServiceCreator(resources); 343 344 345 // For non-proxyable services, we immediately create the service implementation 346 // and return it. There's no interface to proxy, which throws out the possibility of 347 // deferred instantiation, service lifecycles, and decorators. 348 349 ServiceLifecycle2 lifecycle = registry.getServiceLifecycle(def.getServiceScope()); 350 351 if (!canBeProxied) 352 { 353 if (lifecycle.requiresProxy()) 354 throw new IllegalArgumentException( 355 String.format( 356 "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.", 357 def.getServiceScope())); 358 359 return creator.createObject(); 360 } 361 362 creator = new OperationTrackingObjectCreator(registry, String.format("Instantiating service %s implementation via %s", serviceId, creator), creator); 363 364 creator = new LifecycleWrappedServiceCreator(lifecycle, resources, creator); 365 366 // Marked services (or services inside marked modules) are not decorated. 367 // TapestryIOCModule prevents decoration of its services. Note that all decorators will decorate 368 // around the aspect interceptor, which wraps around the core service implementation. 369 370 boolean allowDecoration = !def.isPreventDecoration(); 371 372 if (allowDecoration) 373 { 374 creator = new AdvisorStackBuilder(def, creator, getAspectDecorator(), registry); 375 creator = new InterceptorStackBuilder(def, creator, registry); 376 } 377 378 // Add a wrapper that checks for recursion. 379 380 creator = new RecursiveServiceCreationCheckWrapper(def, creator, logger); 381 382 creator = new OperationTrackingObjectCreator(registry, "Realizing service " + serviceId, creator); 383 384 JustInTimeObjectCreator delegate = new JustInTimeObjectCreator(tracker, creator, serviceId); 385 386 Object proxy = createProxy(resources, delegate, def.isPreventDecoration()); 387 388 registry.addRegistryShutdownListener(delegate); 389 390 if (def.isEagerLoad()) 391 eagerLoadProxies.add(delegate); 392 393 tracker.setStatus(serviceId, Status.VIRTUAL); 394 395 return proxy; 396 } catch (Exception ex) 397 { 398 ex.printStackTrace(); 399 throw new RuntimeException(IOCMessages.errorBuildingService(serviceId, def, ex), ex); 400 } 401 } 402 }; 403 404 return registry.invoke(description, operation); 405 } 406 407 private AspectDecorator getAspectDecorator() 408 { 409 return registry.invoke("Obtaining AspectDecorator service", new Invokable<AspectDecorator>() 410 { 411 @Override 412 public AspectDecorator invoke() 413 { 414 return registry.getService(AspectDecorator.class); 415 } 416 }); 417 } 418 419 private final Runnable instantiateModule = new Runnable() 420 { 421 @Override 422 public void run() 423 { 424 moduleInstance = registry.invoke("Constructing module class " + moduleDef.getBuilderClass().getName(), 425 new Invokable() 426 { 427 @Override 428 public Object invoke() 429 { 430 return instantiateModuleInstance(); 431 } 432 }); 433 } 434 }; 435 436 private final Invokable provideModuleInstance = new Invokable<Object>() 437 { 438 @Override 439 public Object invoke() 440 { 441 if (moduleInstance == null) 442 BARRIER.withWrite(instantiateModule); 443 444 return moduleInstance; 445 } 446 }; 447 448 @Override 449 public Object getModuleBuilder() 450 { 451 return BARRIER.withRead(provideModuleInstance); 452 } 453 454 private Object instantiateModuleInstance() 455 { 456 Class moduleClass = moduleDef.getBuilderClass(); 457 458 Constructor[] constructors = moduleClass.getConstructors(); 459 460 if (constructors.length == 0) 461 throw new RuntimeException(IOCMessages.noPublicConstructors(moduleClass)); 462 463 if (constructors.length > 1) 464 { 465 // Sort the constructors ascending by number of parameters (descending); this is really 466 // just to allow the test suite to work properly across different JVMs (which will 467 // often order the constructors differently). 468 469 Comparator<Constructor> comparator = new Comparator<Constructor>() 470 { 471 @Override 472 public int compare(Constructor c1, Constructor c2) 473 { 474 return c2.getParameterTypes().length - c1.getParameterTypes().length; 475 } 476 }; 477 478 Arrays.sort(constructors, comparator); 479 480 logger.warn(IOCMessages.tooManyPublicConstructors(moduleClass, constructors[0])); 481 } 482 483 Constructor constructor = constructors[0]; 484 485 if (insideConstructor) 486 throw new RuntimeException(IOCMessages.recursiveModuleConstructor(moduleClass, constructor)); 487 488 ObjectLocator locator = new ObjectLocatorImpl(registry, this); 489 Map<Class, Object> resourcesMap = CollectionFactory.newMap(); 490 491 resourcesMap.put(Logger.class, logger); 492 resourcesMap.put(ObjectLocator.class, locator); 493 resourcesMap.put(OperationTracker.class, registry); 494 495 InjectionResources resources = new MapInjectionResources(resourcesMap); 496 497 Throwable fail = null; 498 499 try 500 { 501 insideConstructor = true; 502 503 ObjectCreator[] parameterValues = InternalUtils.calculateParameters(locator, resources, 504 constructor.getParameterTypes(), constructor.getGenericParameterTypes(), 505 constructor.getParameterAnnotations(), registry); 506 507 Object[] realized = InternalUtils.realizeObjects(parameterValues); 508 509 Object result = constructor.newInstance(realized); 510 511 InternalUtils.injectIntoFields(result, locator, resources, registry); 512 513 return result; 514 } catch (InvocationTargetException ex) 515 { 516 fail = ex.getTargetException(); 517 } catch (Exception ex) 518 { 519 fail = ex; 520 } finally 521 { 522 insideConstructor = false; 523 } 524 525 throw new RuntimeException(IOCMessages.instantiateBuilderError(moduleClass, fail), fail); 526 } 527 528 private Object createProxy(ServiceResources resources, ObjectCreator creator, boolean preventDecoration) 529 { 530 String serviceId = resources.getServiceId(); 531 Class serviceInterface = resources.getServiceInterface(); 532 533 String toString = format("<Proxy for %s(%s)>", serviceId, serviceInterface.getName()); 534 535 ServiceProxyToken token = SerializationSupport.createToken(serviceId); 536 537 final Class serviceImplementation = preventDecoration || serviceInterface == TypeCoercer.class ? null : resources.getServiceImplementation(); 538 return createProxyInstance(creator, token, serviceInterface, serviceImplementation, toString); 539 } 540 541 private Object createProxyInstance(final ObjectCreator creator, final ServiceProxyToken token, 542 final Class serviceInterface, final Class serviceImplementation, final String description) 543 { 544 ClassInstantiator instantiator = proxyFactory.createProxy(serviceInterface, serviceImplementation, new PlasticClassTransformer() 545 { 546 @Override 547 public void transform(final PlasticClass plasticClass) 548 { 549 plasticClass.introduceInterface(Serializable.class); 550 551 final PlasticField creatorField = plasticClass.introduceField(ObjectCreator.class, "creator").inject( 552 creator); 553 554 final PlasticField tokenField = plasticClass.introduceField(ServiceProxyToken.class, "token").inject( 555 token); 556 557 PlasticMethod delegateMethod = plasticClass.introducePrivateMethod(serviceInterface.getName(), 558 "delegate", null, null); 559 560 // If not concerned with efficiency, this might be done with method advice instead. 561 delegateMethod.changeImplementation(new InstructionBuilderCallback() 562 { 563 @Override 564 public void doBuild(InstructionBuilder builder) 565 { 566 builder.loadThis().getField(creatorField); 567 builder.invoke(ObjectCreator.class, Object.class, "createObject").checkcast(serviceInterface) 568 .returnResult(); 569 } 570 }); 571 572 plasticClass.proxyInterface(serviceInterface, delegateMethod); 573 574 plasticClass.introduceMethod(WRITE_REPLACE).changeImplementation(new InstructionBuilderCallback() 575 { 576 @Override 577 public void doBuild(InstructionBuilder builder) 578 { 579 builder.loadThis().getField(tokenField).returnResult(); 580 } 581 }); 582 583 plasticClass.addToString(description); 584 } 585 }, false); 586 587 return instantiator.newInstance(); 588 } 589 590 @Override 591 @SuppressWarnings("all") 592 public Set<ContributionDef2> getContributorDefsForService(ServiceDef serviceDef) 593 { 594 Set<ContributionDef2> result = CollectionFactory.newSet(); 595 596 for (ContributionDef next : moduleDef.getContributionDefs()) 597 { 598 ContributionDef2 def = InternalUtils.toContributionDef2(next); 599 600 if (serviceDef.getServiceId().equalsIgnoreCase(def.getServiceId())) 601 { 602 result.add(def); 603 } else 604 { 605 if (markerMatched(serviceDef, def)) 606 { 607 result.add(def); 608 } 609 } 610 } 611 612 return result; 613 } 614 615 private boolean markerMatched(ServiceDef serviceDef, Markable markable) 616 { 617 final Class markableInterface = markable.getServiceInterface(); 618 619 if (markableInterface == null || !markableInterface.isAssignableFrom(serviceDef.getServiceInterface())) 620 return false; 621 622 Set<Class> contributionMarkers = CollectionFactory.newSet(markable.getMarkers()); 623 624 if (contributionMarkers.contains(Local.class)) 625 { 626 // If @Local is present, filter out services that aren't in the same module. 627 // Don't consider @Local to be a marker annotation 628 // for the later match, however. 629 630 if (!isLocalServiceDef(serviceDef)) 631 return false; 632 633 contributionMarkers.remove(Local.class); 634 } 635 636 // Filter out any stray annotations that aren't used by some 637 // service, in any module, as a marker annotation. 638 639 contributionMarkers.retainAll(registry.getMarkerAnnotations()); 640 641 //@Advise and @Decorate default to Object.class service interface. 642 //If @Match is present, no marker annotations are needed. 643 //In such a case an empty contribution marker list should be ignored. 644 if (markableInterface == Object.class && contributionMarkers.isEmpty()) 645 return false; 646 647 return serviceDef.getMarkers().containsAll(contributionMarkers); 648 } 649 650 private boolean isLocalServiceDef(ServiceDef serviceDef) 651 { 652 return serviceDefs.containsKey(serviceDef.getServiceId()); 653 } 654 655 @Override 656 public ServiceDef3 getServiceDef(String serviceId) 657 { 658 return serviceDefs.get(serviceId); 659 } 660 661 @Override 662 public String getLoggerName() 663 { 664 return moduleDef.getLoggerName(); 665 } 666 667 @Override 668 public String toString() 669 { 670 return String.format("ModuleImpl[%s]", moduleDef.getLoggerName()); 671 } 672 673}