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