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