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