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