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 }