001: // Copyright 2006, 2007 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.tapestry.ioc.internal;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
020: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
021:
022: import java.lang.reflect.Constructor;
023: import java.lang.reflect.InvocationTargetException;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029:
030: import org.apache.commons.logging.Log;
031: import org.apache.tapestry.ioc.AnnotationProvider;
032: import org.apache.tapestry.ioc.Configuration;
033: import org.apache.tapestry.ioc.IOCConstants;
034: import org.apache.tapestry.ioc.LogSource;
035: import org.apache.tapestry.ioc.MappedConfiguration;
036: import org.apache.tapestry.ioc.ObjectLocator;
037: import org.apache.tapestry.ioc.ObjectProvider;
038: import org.apache.tapestry.ioc.OrderedConfiguration;
039: import org.apache.tapestry.ioc.Registry;
040: import org.apache.tapestry.ioc.ServiceDecorator;
041: import org.apache.tapestry.ioc.ServiceLifecycle;
042: import org.apache.tapestry.ioc.ServiceResources;
043: import org.apache.tapestry.ioc.def.ContributionDef;
044: import org.apache.tapestry.ioc.def.DecoratorDef;
045: import org.apache.tapestry.ioc.def.ModuleDef;
046: import org.apache.tapestry.ioc.def.ServiceDef;
047: import org.apache.tapestry.ioc.internal.services.RegistryShutdownHubImpl;
048: import org.apache.tapestry.ioc.internal.services.ThreadCleanupHubImpl;
049: import org.apache.tapestry.ioc.internal.util.InternalUtils;
050: import org.apache.tapestry.ioc.internal.util.OneShotLock;
051: import org.apache.tapestry.ioc.internal.util.Orderer;
052: import org.apache.tapestry.ioc.services.ClassFab;
053: import org.apache.tapestry.ioc.services.ClassFactory;
054: import org.apache.tapestry.ioc.services.RegistryShutdownHub;
055: import org.apache.tapestry.ioc.services.RegistryShutdownListener;
056: import org.apache.tapestry.ioc.services.ServiceLifecycleSource;
057: import org.apache.tapestry.ioc.services.SymbolSource;
058: import org.apache.tapestry.ioc.services.TapestryIOCModule;
059: import org.apache.tapestry.ioc.services.ThreadCleanupHub;
060:
061: public class RegistryImpl implements Registry, InternalRegistry {
062: private static final String SYMBOL_SOURCE_SERVICE_ID = "SymbolSource";
063:
064: private static final String REGISTRY_SHUTDOWN_HUB_SERVICE_ID = "RegistryShutdownHub";
065:
066: static final String THREAD_CLEANUP_HUB_SERVICE_ID = "ThreadCleanupHub";
067:
068: /**
069: * Used to obtain the {@link org.apache.tapestry.ioc.services.ClassFactory} service, which is
070: * crucial when creating runtime classes for proxies and the like.
071: */
072: static final String CLASS_FACTORY_SERVICE_ID = "ClassFactory";
073:
074: static final String LOG_SOURCE_SERVICE_ID = "LogSource";
075:
076: private final OneShotLock _lock = new OneShotLock();
077:
078: private final OneShotLock _eagerLoadLock = new OneShotLock();
079:
080: private final Map<String, Object> _builtinServices = newCaseInsensitiveMap();
081:
082: private final Map<String, Class> _builtinTypes = newCaseInsensitiveMap();
083:
084: private final RegistryShutdownHubImpl _registryShutdownHub;
085:
086: private final LogSource _logSource;
087:
088: /** Map from service id to the Module that contains the service. */
089: private final Map<String, Module> _serviceIdToModule = newCaseInsensitiveMap();
090:
091: private final Map<String, ServiceLifecycle> _lifecycles = newCaseInsensitiveMap();
092:
093: private final ThreadCleanupHubImpl _cleanupHub;
094:
095: private final ClassFactory _classFactory;
096:
097: private SymbolSource _symbolSource;
098:
099: private final List<Module> _modules = newList();
100:
101: public static final class OrderedConfigurationToOrdererAdaptor<T>
102: implements OrderedConfiguration<T> {
103: private final Orderer<T> _orderer;
104:
105: public OrderedConfigurationToOrdererAdaptor(Orderer<T> orderer) {
106: _orderer = orderer;
107: }
108:
109: public void add(String id, T object, String... constraints) {
110: _orderer.add(id, object, constraints);
111: }
112: }
113:
114: /**
115: * Constructs the registry from a set of module definitions and other resources.
116: *
117: * @param moduleDefs
118: * defines the modules (and builders, decorators, etc., within)
119: * @param classFactory
120: * TODO
121: * @param logSource
122: * used to obtain Log instances
123: */
124: public RegistryImpl(Collection<ModuleDef> moduleDefs,
125: ClassFactory classFactory, LogSource logSource) {
126: _logSource = logSource;
127:
128: for (ModuleDef def : moduleDefs) {
129: Log log = _logSource.getLog(def.getLogName());
130:
131: Module module = new ModuleImpl(this , def, classFactory, log);
132:
133: _modules.add(module);
134:
135: for (String serviceId : def.getServiceIds()) {
136: Module existing = _serviceIdToModule.get(serviceId);
137:
138: if (existing != null)
139: throw new RuntimeException(IOCMessages
140: .serviceIdConflict(serviceId, existing
141: .getServiceDef(serviceId), module
142: .getServiceDef(serviceId)));
143:
144: _serviceIdToModule.put(serviceId, module);
145: }
146: }
147:
148: addBuiltin(LOG_SOURCE_SERVICE_ID, LogSource.class, _logSource);
149:
150: _classFactory = classFactory;
151:
152: addBuiltin(CLASS_FACTORY_SERVICE_ID, ClassFactory.class,
153: _classFactory);
154:
155: Log log = logForBuiltinService(THREAD_CLEANUP_HUB_SERVICE_ID);
156:
157: _cleanupHub = new ThreadCleanupHubImpl(log);
158:
159: addBuiltin(THREAD_CLEANUP_HUB_SERVICE_ID,
160: ThreadCleanupHub.class, _cleanupHub);
161:
162: log = logForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID);
163:
164: _registryShutdownHub = new RegistryShutdownHubImpl(log);
165:
166: addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID,
167: RegistryShutdownHub.class, _registryShutdownHub);
168:
169: _lifecycles.put("singleton", new SingletonServiceLifecycle());
170: }
171:
172: /**
173: * It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which
174: * point we raise issues about improper publishing of the Registry instance from the
175: * RegistryImpl constructor. Moving eager loading of services out to its own method should
176: * ensure thread safety.
177: */
178: public void eagerLoadServices() {
179: _eagerLoadLock.lock();
180:
181: for (Module m : _modules)
182: m.eagerLoadServices();
183:
184: cleanupThread();
185: }
186:
187: public Log logForService(String serviceId) {
188: Module module = _serviceIdToModule.get(serviceId);
189:
190: assert module != null;
191:
192: return _logSource.getLog(module.getLogName() + "." + serviceId);
193: }
194:
195: private Log logForBuiltinService(String serviceId) {
196: return _logSource.getLog(TapestryIOCModule.class + "."
197: + serviceId);
198: }
199:
200: private <T> void addBuiltin(String serviceId,
201: Class<T> serviceInterface, T service) {
202: _builtinTypes.put(serviceId, serviceInterface);
203: _builtinServices.put(serviceId, service);
204: }
205:
206: public synchronized void shutdown() {
207: _lock.lock();
208:
209: _registryShutdownHub.fireRegistryDidShutdown();
210: }
211:
212: /** Internal access, usually from another module. */
213: public <T> T getService(String serviceId, Class<T> serviceInterface) {
214: _lock.check();
215:
216: T result = checkForBuiltinService(serviceId, serviceInterface);
217: if (result != null)
218: return result;
219:
220: // Checking serviceId and serviceInterface is overkill; they have been checked and rechecked
221: // all the way to here.
222:
223: Module containingModule = locateModuleForService(serviceId);
224:
225: return containingModule.getService(serviceId, serviceInterface);
226: }
227:
228: private <T> T checkForBuiltinService(String serviceId,
229: Class<T> serviceInterface) {
230: Object service = _builtinServices.get(serviceId);
231:
232: if (service == null)
233: return null;
234:
235: try {
236: return serviceInterface.cast(service);
237: } catch (ClassCastException ex) {
238: throw new RuntimeException(IOCMessages
239: .serviceWrongInterface(serviceId, _builtinTypes
240: .get(serviceId), serviceInterface));
241: }
242: }
243:
244: public void cleanupThread() {
245: _lock.check();
246:
247: _cleanupHub.cleanup();
248: }
249:
250: private Module locateModuleForService(String serviceId) {
251: Module module = _serviceIdToModule.get(serviceId);
252:
253: if (module == null)
254: throw new RuntimeException(IOCMessages.noSuchService(
255: serviceId, _serviceIdToModule.keySet()));
256:
257: return module;
258: }
259:
260: public <T> Collection<T> getUnorderedConfiguration(
261: ServiceDef serviceDef, Class<T> objectType) {
262: _lock.check();
263:
264: final Collection<T> result = newList();
265:
266: Configuration<T> configuration = new Configuration<T>() {
267: public void add(T object) {
268: result.add(object);
269: }
270: };
271:
272: Collection<Module> modules = _modules;
273:
274: for (Module m : modules)
275: addToUnorderedConfiguration(configuration, objectType,
276: serviceDef, m);
277:
278: return result;
279: }
280:
281: public <T> List<T> getOrderedConfiguration(ServiceDef serviceDef,
282: Class<T> objectType) {
283: _lock.check();
284:
285: Log log = null;
286:
287: final Orderer<T> orderer = new Orderer<T>(log);
288:
289: OrderedConfiguration<T> configuration = new OrderedConfigurationToOrdererAdaptor<T>(
290: orderer);
291:
292: Collection<Module> modules = _modules;
293:
294: for (Module m : modules)
295: addToOrderedConfiguration(configuration, objectType,
296: serviceDef, m);
297:
298: return orderer.getOrdered();
299: }
300:
301: public <K, V> Map<K, V> getMappedConfiguration(
302: ServiceDef serviceDef, Class<K> keyType, Class<V> objectType) {
303: _lock.check();
304:
305: // When the key type is String, then a case insensitive map is used for both cases.
306:
307: final Map<K, V> result = newConfigurationMap(keyType);
308: Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType);
309:
310: MappedConfiguration<K, V> configuration = new MappedConfiguration<K, V>() {
311: public void add(K key, V value) {
312: result.put(key, value);
313: }
314: };
315:
316: Collection<Module> modules = _modules;
317:
318: for (Module m : modules)
319: addToMappedConfiguration(configuration, keyToContribution,
320: keyType, objectType, serviceDef, m);
321:
322: return result;
323: }
324:
325: @SuppressWarnings("unchecked")
326: private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType) {
327: if (keyType.equals(String.class)) {
328: Map<String, K> result = newCaseInsensitiveMap();
329:
330: return (Map<K, V>) result;
331: }
332:
333: return newMap();
334: }
335:
336: private <K, V> void addToMappedConfiguration(
337: MappedConfiguration<K, V> configuration,
338: Map<K, ContributionDef> keyToContribution,
339: Class<K> keyClass, Class<V> valueType,
340: ServiceDef serviceDef, Module module) {
341: String serviceId = serviceDef.getServiceId();
342: Set<ContributionDef> contributions = module
343: .getContributorDefsForService(serviceId);
344:
345: if (contributions.isEmpty())
346: return;
347:
348: Log log = logForService(serviceId);
349:
350: boolean debug = log.isDebugEnabled();
351:
352: ObjectLocator locator = new ServiceResourcesImpl(this , module,
353: serviceDef, _classFactory, log);
354:
355: for (ContributionDef def : contributions) {
356: MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(
357: serviceId, def, log, keyClass, valueType,
358: keyToContribution, configuration);
359:
360: if (debug)
361: log.debug(IOCMessages.invokingMethod(def));
362:
363: def.contribute(module, locator, validating);
364: }
365:
366: }
367:
368: private <T> void addToUnorderedConfiguration(
369: Configuration<T> configuration, Class<T> valueType,
370: ServiceDef serviceDef, Module module) {
371: String serviceId = serviceDef.getServiceId();
372: Set<ContributionDef> contributions = module
373: .getContributorDefsForService(serviceId);
374:
375: if (contributions.isEmpty())
376: return;
377:
378: Log log = logForService(serviceId);
379:
380: boolean debug = log.isDebugEnabled();
381:
382: ObjectLocator locator = new ServiceResourcesImpl(this , module,
383: serviceDef, _classFactory, log);
384:
385: for (ContributionDef def : contributions) {
386: Configuration<T> validating = new ValidatingConfigurationWrapper<T>(
387: serviceId, log, valueType, def, configuration);
388:
389: if (debug)
390: log.debug(IOCMessages.invokingMethod(def));
391:
392: def.contribute(module, locator, validating);
393: }
394: }
395:
396: private <T> void addToOrderedConfiguration(
397: OrderedConfiguration<T> configuration, Class<T> valueType,
398: ServiceDef serviceDef, Module module) {
399: String serviceId = serviceDef.getServiceId();
400: Set<ContributionDef> contributions = module
401: .getContributorDefsForService(serviceId);
402:
403: if (contributions.isEmpty())
404: return;
405:
406: Log log = logForService(serviceId);
407: boolean debug = log.isDebugEnabled();
408:
409: ObjectLocator locator = new ServiceResourcesImpl(this , module,
410: serviceDef, _classFactory, log);
411:
412: for (ContributionDef def : contributions) {
413: OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(
414: serviceId, def, log, valueType, configuration);
415:
416: if (debug)
417: log.debug(IOCMessages.invokingMethod(def));
418:
419: def.contribute(module, locator, validating);
420: }
421: }
422:
423: public <T> T getService(Class<T> serviceInterface) {
424: _lock.check();
425:
426: List<String> serviceIds = findServiceIdsForInterface(serviceInterface);
427:
428: if (serviceIds == null)
429: serviceIds = Collections.emptyList();
430:
431: switch (serviceIds.size()) {
432: case 0:
433:
434: throw new RuntimeException(IOCMessages
435: .noServiceMatchesType(serviceInterface));
436:
437: case 1:
438:
439: String serviceId = serviceIds.get(0);
440:
441: return getService(serviceId, serviceInterface);
442:
443: default:
444:
445: Collections.sort(serviceIds);
446:
447: throw new RuntimeException(IOCMessages.manyServiceMatches(
448: serviceInterface, serviceIds));
449: }
450: }
451:
452: private List<String> findServiceIdsForInterface(
453: Class serviceInterface) {
454: List<String> result = newList();
455:
456: for (Module module : _modules)
457: result.addAll(module
458: .findServiceIdsForInterface(serviceInterface));
459:
460: for (Map.Entry<String, Object> entry : _builtinServices
461: .entrySet()) {
462: if (serviceInterface.isInstance(entry.getValue()))
463: result.add(entry.getKey());
464: }
465:
466: Collections.sort(result);
467:
468: return result;
469: }
470:
471: public ServiceLifecycle getServiceLifecycle(String scope) {
472: _lock.check();
473:
474: ServiceLifecycle result = _lifecycles.get(scope);
475:
476: if (result == null) {
477: ServiceLifecycleSource source = getService(
478: "ServiceLifecycleSource",
479: ServiceLifecycleSource.class);
480: result = source.get(scope);
481: }
482:
483: if (result == null)
484: throw new RuntimeException(IOCMessages.unknownScope(scope));
485:
486: return result;
487: }
488:
489: public List<ServiceDecorator> findDecoratorsForService(
490: ServiceDef serviceDef) {
491: _lock.check();
492:
493: assert serviceDef != null;
494:
495: Log log = logForService(serviceDef.getServiceId());
496:
497: Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(
498: log);
499:
500: for (Module module : _modules) {
501: Set<DecoratorDef> decorators = module
502: .findMatchingDecoratorDefs(serviceDef);
503:
504: if (decorators.isEmpty())
505: continue;
506:
507: ServiceResources resources = new ServiceResourcesImpl(this ,
508: module, serviceDef, _classFactory, log);
509:
510: for (DecoratorDef dd : decorators) {
511: ServiceDecorator sd = dd.createDecorator(module,
512: resources);
513:
514: orderer.add(dd.getDecoratorId(), sd, dd
515: .getConstraints());
516: }
517: }
518:
519: return orderer.getOrdered();
520: }
521:
522: public ClassFab newClass(Class serviceInterface) {
523: _lock.check();
524:
525: return _classFactory.newClass(serviceInterface);
526: }
527:
528: private <T> T getObject(Class<T> objectType,
529: AnnotationProvider annotationProvider, ObjectLocator locator) {
530: _lock.check();
531:
532: ObjectProvider masterProvider = getService(
533: IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
534: ObjectProvider.class);
535:
536: AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider
537: : new NullAnnotationProvider();
538:
539: return masterProvider.provide(objectType, effectiveProvider,
540: locator);
541: }
542:
543: public <T> T getObject(Class<T> objectType,
544: AnnotationProvider annotationProvider) {
545: _lock.check();
546:
547: return getObject(objectType, annotationProvider, this );
548: }
549:
550: public void addRegistryShutdownListener(
551: RegistryShutdownListener listener) {
552: _lock.check();
553:
554: _registryShutdownHub.addRegistryShutdownListener(listener);
555: }
556:
557: public String expandSymbols(String input) {
558: _lock.check();
559:
560: // Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary.
561:
562: if (!InternalUtils.containsSymbols(input))
563: return input;
564:
565: return getSymbolSource().expandSymbols(input);
566: }
567:
568: /** Defers obtaining the symbol source until actually needed. */
569: private synchronized SymbolSource getSymbolSource() {
570: if (_symbolSource == null)
571: _symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID,
572: SymbolSource.class);
573:
574: return _symbolSource;
575: }
576:
577: public <T> T autobuild(Class<T> clazz) {
578: notNull(clazz, "clazz");
579:
580: Constructor constructor = InternalUtils
581: .findAutobuildConstructor(clazz);
582:
583: if (constructor == null)
584: throw new RuntimeException(IOCMessages
585: .noAutobuildConstructor(clazz));
586:
587: Throwable failure = null;
588: // An empty map, because when performing autobuilding outside the context of building a
589: // service, we don't have defaults for Log, service id, etc.
590:
591: Map<Class, Object> empty = Collections.emptyMap();
592:
593: try {
594: Object[] parameters = InternalUtils
595: .calculateParametersForConstructor(constructor,
596: this , empty);
597:
598: return clazz.cast(constructor.newInstance(parameters));
599: } catch (InvocationTargetException ite) {
600: failure = ite.getTargetException();
601: } catch (Exception ex) {
602: failure = ex;
603: }
604:
605: String description = _classFactory.getConstructorLocation(
606: constructor).toString();
607:
608: throw new RuntimeException(IOCMessages
609: .autobuildConstructorError(description, failure),
610: failure);
611:
612: }
613:
614: }
|