blob: 51ca08ace6d3598aec891888d99aac389d200598 [file] [log] [blame]
// Copyright 2006-2014 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.ioc.internal;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Flow;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.func.Predicate;
import org.apache.tapestry5.ioc.*;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.def.*;
import org.apache.tapestry5.ioc.internal.services.PerthreadManagerImpl;
import org.apache.tapestry5.ioc.internal.services.RegistryShutdownHubImpl;
import org.apache.tapestry5.ioc.internal.util.*;
import org.apache.tapestry5.ioc.modules.TapestryIOCModule;
import org.apache.tapestry5.ioc.services.*;
import org.apache.tapestry5.ioc.util.AvailableValues;
import org.apache.tapestry5.ioc.util.UnknownValueException;
import org.apache.tapestry5.services.UpdateListenerHub;
import org.slf4j.Logger;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
@SuppressWarnings("all")
public class RegistryImpl implements Registry, InternalRegistry, ServiceProxyProvider
{
private static final String SYMBOL_SOURCE_SERVICE_ID = "SymbolSource";
private static final String REGISTRY_SHUTDOWN_HUB_SERVICE_ID = "RegistryShutdownHub";
static final String PERTHREAD_MANAGER_SERVICE_ID = "PerthreadManager";
private static final String SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID = "ServiceActivityScoreboard";
/**
* The set of marker annotations for a builtin service.
*/
private final static Set<Class> BUILTIN = CollectionFactory.newSet();
// Split create/assign to appease generics gods
static
{
BUILTIN.add(Builtin.class);
}
static final String PLASTIC_PROXY_FACTORY_SERVICE_ID = "PlasticProxyFactory";
static final String LOGGER_SOURCE_SERVICE_ID = "LoggerSource";
private final OneShotLock lock = new OneShotLock();
private final OneShotLock eagerLoadLock = new OneShotLock();
private final Map<String, Object> builtinServices = CollectionFactory.newCaseInsensitiveMap();
private final Map<String, Class> builtinTypes = CollectionFactory.newCaseInsensitiveMap();
private final RegistryShutdownHubImpl registryShutdownHub;
private final LoggerSource loggerSource;
/**
* Map from service id to the Module that contains the service.
*/
private final Map<String, Module> serviceIdToModule = CollectionFactory.newCaseInsensitiveMap();
private final Map<String, ServiceLifecycle2> lifecycles = CollectionFactory.newCaseInsensitiveMap();
private final PerthreadManager perthreadManager;
private final PlasticProxyFactory proxyFactory;
private final ServiceActivityTracker tracker;
private SymbolSource symbolSource;
private final Map<Module, Set<ServiceDef2>> moduleToServiceDefs = CollectionFactory.newMap();
/**
* From marker type to a list of marked service instances.
*/
private final Map<Class, List<ServiceDef2>> markerToServiceDef = CollectionFactory.newMap();
private final Set<ServiceDef2> allServiceDefs = CollectionFactory.newSet();
private final OperationTracker operationTracker;
private final TypeCoercerProxy typeCoercerProxy = new TypeCoercerProxyImpl(this);
private final Map<Class<? extends Annotation>, Annotation> cachedAnnotationProxies = CollectionFactory.newConcurrentMap();
private final Set<Runnable> startups = CollectionFactory.newSet();
private DelegatingServiceConfigurationListener serviceConfigurationListener;
/**
* Constructs the registry from a set of module definitions and other resources.
*
* @param moduleDefs
* defines the modules (and builders, decorators, etc., within)
* @param proxyFactory
* used to create new proxy objects
* @param loggerSource
* used to obtain Logger instances
* @param operationTracker
*/
public RegistryImpl(Collection<ModuleDef2> moduleDefs, PlasticProxyFactory proxyFactory,
LoggerSource loggerSource, OperationTracker operationTracker)
{
assert moduleDefs != null;
assert proxyFactory != null;
assert loggerSource != null;
assert operationTracker != null;
this.loggerSource = loggerSource;
this.operationTracker = operationTracker;
this.proxyFactory = proxyFactory;
serviceConfigurationListener = new DelegatingServiceConfigurationListener(
loggerForBuiltinService(ServiceConfigurationListener.class.getSimpleName()));
Logger logger = loggerForBuiltinService(PERTHREAD_MANAGER_SERVICE_ID);
PerthreadManagerImpl ptmImpl = new PerthreadManagerImpl(logger);
perthreadManager = ptmImpl;
final ServiceActivityTrackerImpl scoreboardAndTracker = new ServiceActivityTrackerImpl(perthreadManager);
tracker = scoreboardAndTracker;
logger = loggerForBuiltinService(REGISTRY_SHUTDOWN_HUB_SERVICE_ID);
registryShutdownHub = new RegistryShutdownHubImpl(logger);
ptmImpl.registerForShutdown(registryShutdownHub);
lifecycles.put("singleton", new SingletonServiceLifecycle());
registryShutdownHub.addRegistryShutdownListener(new Runnable()
{
@Override
public void run()
{
scoreboardAndTracker.shutdown();
}
});
for (ModuleDef2 def : moduleDefs)
{
logger = this.loggerSource.getLogger(def.getLoggerName());
Module module = new ModuleImpl(this, tracker, def, proxyFactory, logger);
Set<ServiceDef2> moduleServiceDefs = CollectionFactory.newSet();
for (String serviceId : def.getServiceIds())
{
ServiceDef2 serviceDef = module.getServiceDef(serviceId);
moduleServiceDefs.add(serviceDef);
allServiceDefs.add(serviceDef);
Module existing = serviceIdToModule.get(serviceId);
if (existing != null)
throw new RuntimeException(IOCMessages.serviceIdConflict(serviceId,
existing.getServiceDef(serviceId), serviceDef));
serviceIdToModule.put(serviceId, module);
// The service is defined but will not have gone further than that.
tracker.define(serviceDef, Status.DEFINED);
for (Class marker : serviceDef.getMarkers())
InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
}
moduleToServiceDefs.put(module, moduleServiceDefs);
addStartupsInModule(def, module, logger);
}
addBuiltin(SERVICE_ACTIVITY_SCOREBOARD_SERVICE_ID, ServiceActivityScoreboard.class, scoreboardAndTracker);
addBuiltin(LOGGER_SOURCE_SERVICE_ID, LoggerSource.class, this.loggerSource);
addBuiltin(PERTHREAD_MANAGER_SERVICE_ID, PerthreadManager.class, perthreadManager);
addBuiltin(REGISTRY_SHUTDOWN_HUB_SERVICE_ID, RegistryShutdownHub.class, registryShutdownHub);
addBuiltin(PLASTIC_PROXY_FACTORY_SERVICE_ID, PlasticProxyFactory.class, proxyFactory);
validateContributeDefs(moduleDefs);
serviceConfigurationListener.setDelegates(getService(ServiceConfigurationListenerHub.class).getListeners());
scoreboardAndTracker.startup();
SerializationSupport.setProvider(this);
}
private void addStartupsInModule(ModuleDef2 def, final Module module, final Logger logger)
{
for (final StartupDef startup : def.getStartups())
{
startups.add(new Runnable()
{
@Override
public void run()
{
startup.invoke(module, RegistryImpl.this, RegistryImpl.this, logger);
}
});
}
}
/**
* Validate that each module's ContributeDefs correspond to an actual service.
*/
private void validateContributeDefs(Collection<ModuleDef2> moduleDefs)
{
for (ModuleDef2 module : moduleDefs)
{
Set<ContributionDef> contributionDefs = module.getContributionDefs();
for (ContributionDef cd : contributionDefs)
{
String serviceId = cd.getServiceId();
ContributionDef3 cd3 = InternalUtils.toContributionDef3(cd);
// Ignore any optional contribution methods; there's no way to validate that
// they contribute to a known service ... that's the point of @Optional
if (cd3.isOptional())
{
continue;
}
// Otherwise, check that the service being contributed to exists ...
if (cd3.getServiceId() != null)
{
if (!serviceIdToModule.containsKey(serviceId))
{
throw new IllegalArgumentException(
IOCMessages.contributionForNonexistentService(cd));
}
} else if (!isContributionForExistentService(module, cd3))
{
throw new IllegalArgumentException(
IOCMessages.contributionForUnqualifiedService(cd3));
}
}
}
}
/**
* Invoked when the contribution method didn't follow the naming convention and so doesn't identify
* a service by id; instead there was an @Contribute to identify the service interface.
*/
@SuppressWarnings("all")
private boolean isContributionForExistentService(ModuleDef moduleDef, final ContributionDef2 cd)
{
final Set<Class> contributionMarkers = new HashSet(cd.getMarkers());
boolean localOnly = contributionMarkers.contains(Local.class);
Flow<ServiceDef2> serviceDefs = localOnly ? getLocalServiceDefs(moduleDef) : F.flow(allServiceDefs);
contributionMarkers.retainAll(getMarkerAnnotations());
contributionMarkers.remove(Local.class);
// Match services with the correct interface AND having as markers *all* the marker annotations
Flow<ServiceDef2> filtered = serviceDefs.filter(F.and(new Predicate<ServiceDef2>()
{
@Override
public boolean accept(ServiceDef2 object)
{
return object.getServiceInterface().equals(cd.getServiceInterface());
}
}, new Predicate<ServiceDef2>()
{
@Override
public boolean accept(ServiceDef2 serviceDef)
{
return serviceDef.getMarkers().containsAll(contributionMarkers);
}
}
));
// That's a lot of logic; the good news is it will short-circuit as soon as it finds a single match,
// thanks to the laziness inside Flow.
return !filtered.isEmpty();
}
private Flow<ServiceDef2> getLocalServiceDefs(final ModuleDef moduleDef)
{
return F.flow(moduleDef.getServiceIds()).map(new Mapper<String, ServiceDef2>()
{
@Override
public ServiceDef2 map(String value)
{
return InternalUtils.toServiceDef2(moduleDef.getServiceDef(value));
}
});
}
/**
* It's not unreasonable for an eagerly-loaded service to decide to start a thread, at which
* point we raise issues
* about improper publishing of the Registry instance from the RegistryImpl constructor. Moving
* eager loading of
* services out to its own method should ensure thread safety.
*/
@Override
public void performRegistryStartup()
{
if (JDKUtils.JDK_1_5)
{
throw new RuntimeException("Your JDK version is too old."
+ " Tapestry requires Java 1.6 or newer since version 5.4.");
}
eagerLoadLock.lock();
List<EagerLoadServiceProxy> proxies = CollectionFactory.newList();
for (Module m : moduleToServiceDefs.keySet())
m.collectEagerLoadServices(proxies);
// TAPESTRY-2267: Gather up all the proxies before instantiating any of them.
for (EagerLoadServiceProxy proxy : proxies)
{
proxy.eagerLoadService();
}
for (Runnable startup : startups) {
startup.run();
}
startups.clear();
getService("RegistryStartup", Runnable.class).run();
cleanupThread();
}
@Override
public Logger getServiceLogger(String serviceId)
{
Module module = serviceIdToModule.get(serviceId);
assert module != null;
return loggerSource.getLogger(module.getLoggerName() + "." + serviceId);
}
private Logger loggerForBuiltinService(String serviceId)
{
return loggerSource.getLogger(TapestryIOCModule.class + "." + serviceId);
}
private <T> void addBuiltin(final String serviceId, final Class<T> serviceInterface, T service)
{
builtinTypes.put(serviceId, serviceInterface);
builtinServices.put(serviceId, service);
// Make sure each of the builtin services is also available via the Builtin annotation
// marker.
ServiceDef2 serviceDef = new ServiceDef2()
{
@Override
public ObjectCreator createServiceCreator(ServiceBuilderResources resources)
{
return null;
}
@Override
public Set<Class> getMarkers()
{
return BUILTIN;
}
@Override
public String getServiceId()
{
return serviceId;
}
@Override
public Class getServiceInterface()
{
return serviceInterface;
}
@Override
public String getServiceScope()
{
return ScopeConstants.DEFAULT;
}
@Override
public boolean isEagerLoad()
{
return false;
}
@Override
public boolean isPreventDecoration()
{
return true;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((serviceId == null) ? 0 : serviceId.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof ServiceDefImpl)) { return false; }
ServiceDef other = (ServiceDef) obj;
if (serviceId == null)
{
if (other.getServiceId() != null) { return false; }
}
else if (!serviceId.equals(other.getServiceId())) { return false; }
return true;
}
};
for (Class marker : serviceDef.getMarkers())
{
InternalUtils.addToMapList(markerToServiceDef, marker, serviceDef);
allServiceDefs.add(serviceDef);
}
tracker.define(serviceDef, Status.BUILTIN);
}
@Override
public synchronized void shutdown()
{
lock.lock();
registryShutdownHub.fireRegistryDidShutdown();
SerializationSupport.clearProvider(this);
}
@Override
public <T> T getService(String serviceId, Class<T> serviceInterface)
{
lock.check();
T result = checkForBuiltinService(serviceId, serviceInterface);
if (result != null)
return result;
// Checking serviceId and serviceInterface is overkill; they have been checked and rechecked
// all the way to here.
Module containingModule = locateModuleForService(serviceId);
return containingModule.getService(serviceId, serviceInterface);
}
private <T> T checkForBuiltinService(String serviceId, Class<T> serviceInterface)
{
Object service = builtinServices.get(serviceId);
if (service == null)
return null;
try
{
return serviceInterface.cast(service);
} catch (ClassCastException ex)
{
throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, builtinTypes.get(serviceId),
serviceInterface));
}
}
@Override
public void cleanupThread()
{
lock.check();
perthreadManager.cleanup();
}
private Module locateModuleForService(String serviceId)
{
Module module = serviceIdToModule.get(serviceId);
if (module == null)
throw new UnknownValueException(String.format("Service id '%s' is not defined by any module.", serviceId),
new AvailableValues("Defined service ids", serviceIdToModule));
return module;
}
@Override
public <T> Collection<T> getUnorderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType)
{
lock.check();
final Collection<T> result = CollectionFactory.newList();
for (Module m : moduleToServiceDefs.keySet())
addToUnorderedConfiguration(result, objectType, serviceDef, m);
if (!isServiceConfigurationListenerServiceDef(serviceDef))
{
serviceConfigurationListener.onUnorderedConfiguration(serviceDef, result);
}
return result;
}
@Override
@SuppressWarnings("unchecked")
public <T> List<T> getOrderedConfiguration(ServiceDef3 serviceDef, Class<T> objectType)
{
lock.check();
String serviceId = serviceDef.getServiceId();
Logger logger = getServiceLogger(serviceId);
Orderer<T> orderer = new Orderer<T>(logger);
Map<String, OrderedConfigurationOverride<T>> overrides = CollectionFactory.newCaseInsensitiveMap();
// TAP5-2129. NOTICE: if someday an ordering between modules is added, this should be reverted
// or a notice added to the documentation.
List<Module> modules = new ArrayList<Module>(moduleToServiceDefs.keySet());
Collections.sort(modules, new ModuleComparator());
for (Module m : modules)
addToOrderedConfiguration(orderer, overrides, objectType, serviceDef, m);
// An ugly hack ... perhaps we should introduce a new builtin service so that this can be
// accomplished in the normal way?
if (serviceId.equals("MasterObjectProvider"))
{
ObjectProvider contribution = new ObjectProvider()
{
@Override
public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
{
return findServiceByMarkerAndType(objectType, annotationProvider, null);
}
};
orderer.add("ServiceByMarker", (T) contribution);
}
for (OrderedConfigurationOverride<T> override : overrides.values())
override.apply();
final List<T> result = orderer.getOrdered();
if (!isServiceConfigurationListenerServiceDef(serviceDef))
{
serviceConfigurationListener.onOrderedConfiguration(serviceDef, result);
}
return result;
}
private boolean isServiceConfigurationListenerServiceDef(ServiceDef serviceDef)
{
return serviceDef.getServiceId().equalsIgnoreCase(ServiceConfigurationListener.class.getSimpleName());
}
@Override
public <K, V> Map<K, V> getMappedConfiguration(ServiceDef3 serviceDef, Class<K> keyType, Class<V> objectType)
{
lock.check();
// When the key type is String, then a case insensitive map is used.
Map<K, V> result = newConfigurationMap(keyType);
Map<K, ContributionDef> keyToContribution = newConfigurationMap(keyType);
Map<K, MappedConfigurationOverride<K, V>> overrides = newConfigurationMap(keyType);
for (Module m : moduleToServiceDefs.keySet())
addToMappedConfiguration(result, overrides, keyToContribution, keyType, objectType, serviceDef, m);
for (MappedConfigurationOverride<K, V> override : overrides.values())
{
override.apply();
}
if (!isServiceConfigurationListenerServiceDef(serviceDef))
{
serviceConfigurationListener.onMappedConfiguration(serviceDef, result);
}
return result;
}
@SuppressWarnings("unchecked")
private <K, V> Map<K, V> newConfigurationMap(Class<K> keyType)
{
if (keyType.equals(String.class))
{
Map<String, K> result = CollectionFactory.newCaseInsensitiveMap();
return (Map<K, V>) result;
}
return CollectionFactory.newMap();
}
private <K, V> void addToMappedConfiguration(Map<K, V> map, Map<K, MappedConfigurationOverride<K, V>> overrides,
Map<K, ContributionDef> keyToContribution, Class<K> keyClass, Class<V> valueType, ServiceDef3 serviceDef,
final Module module)
{
String serviceId = serviceDef.getServiceId();
Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
if (contributions.isEmpty())
return;
Logger logger = getServiceLogger(serviceId);
final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
for (final ContributionDef def : contributions)
{
final MappedConfiguration<K, V> validating = new ValidatingMappedConfigurationWrapper<K, V>(valueType,
resources, typeCoercerProxy, map, overrides, serviceId, def, keyClass, keyToContribution);
String description = "Invoking " + def;
logger.debug(description);
operationTracker.run(description, new Runnable()
{
@Override
public void run()
{
def.contribute(module, resources, validating);
}
});
}
}
private <T> void addToUnorderedConfiguration(Collection<T> collection, Class<T> valueType, ServiceDef3 serviceDef,
final Module module)
{
String serviceId = serviceDef.getServiceId();
Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
if (contributions.isEmpty())
return;
Logger logger = getServiceLogger(serviceId);
final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
for (final ContributionDef def : contributions)
{
final Configuration<T> validating = new ValidatingConfigurationWrapper<T>(valueType, resources,
typeCoercerProxy, collection, serviceId);
String description = "Invoking " + def;
logger.debug(description);
operationTracker.run(description, new Runnable()
{
@Override
public void run()
{
def.contribute(module, resources, validating);
}
});
}
}
private <T> void addToOrderedConfiguration(Orderer<T> orderer,
Map<String, OrderedConfigurationOverride<T>> overrides, Class<T> valueType, ServiceDef3 serviceDef,
final Module module)
{
String serviceId = serviceDef.getServiceId();
Set<ContributionDef2> contributions = module.getContributorDefsForService(serviceDef);
if (contributions.isEmpty())
return;
Logger logger = getServiceLogger(serviceId);
final ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
for (final ContributionDef def : contributions)
{
final OrderedConfiguration<T> validating = new ValidatingOrderedConfigurationWrapper<T>(valueType,
resources, typeCoercerProxy, orderer, overrides, def);
String description = "Invoking " + def;
logger.debug(description);
operationTracker.run(description, new Runnable()
{
@Override
public void run()
{
def.contribute(module, resources, validating);
}
});
}
}
@Override
public <T> T getService(Class<T> serviceInterface)
{
lock.check();
return getServiceByTypeAndMarkers(serviceInterface);
}
@Override
public <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
{
lock.check();
return getServiceByTypeAndMarkers(serviceInterface, markerTypes);
}
private <T> T getServiceByTypeAlone(Class<T> serviceInterface)
{
List<String> serviceIds = findServiceIdsForInterface(serviceInterface);
if (serviceIds == null)
serviceIds = Collections.emptyList();
switch (serviceIds.size())
{
case 0:
throw new RuntimeException(IOCMessages.noServiceMatchesType(serviceInterface));
case 1:
String serviceId = serviceIds.get(0);
return getService(serviceId, serviceInterface);
default:
Collections.sort(serviceIds);
throw new RuntimeException(IOCMessages.manyServiceMatches(serviceInterface, serviceIds));
}
}
private <T> T getServiceByTypeAndMarkers(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes)
{
if (markerTypes.length == 0)
{
return getServiceByTypeAlone(serviceInterface);
}
AnnotationProvider provider = createAnnotationProvider(markerTypes);
Set<ServiceDef2> matches = CollectionFactory.newSet();
List<Class> markers = CollectionFactory.newList();
findServiceDefsMatchingMarkerAndType(serviceInterface, provider, null, markers, matches);
return extractServiceFromMatches(serviceInterface, markers, matches);
}
private AnnotationProvider createAnnotationProvider(Class<? extends Annotation>... markerTypes)
{
final Map<Class<? extends Annotation>, Annotation> map = CollectionFactory.newMap();
for (Class<? extends Annotation> markerType : markerTypes)
{
map.put(markerType, createAnnotationProxy(markerType));
}
return new AnnotationProvider()
{
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
{
return annotationClass.cast(map.get(annotationClass));
}
};
}
private <A extends Annotation> Annotation createAnnotationProxy(final Class<A> annotationType)
{
Annotation result = cachedAnnotationProxies.get(annotationType);
if (result == null)
{
// We create a JDK proxy because its pretty quick and easy.
InvocationHandler handler = new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.getName().equals("annotationType"))
{
return annotationType;
}
return method.invoke(proxy, args);
}
};
result = (Annotation) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{annotationType},
handler);
cachedAnnotationProxies.put(annotationType, result);
}
return result;
}
private List<String> findServiceIdsForInterface(Class serviceInterface)
{
List<String> result = CollectionFactory.newList();
for (Module module : moduleToServiceDefs.keySet())
result.addAll(module.findServiceIdsForInterface(serviceInterface));
for (Map.Entry<String, Object> entry : builtinServices.entrySet())
{
if (serviceInterface.isInstance(entry.getValue()))
result.add(entry.getKey());
}
Collections.sort(result);
return result;
}
@Override
public ServiceLifecycle2 getServiceLifecycle(String scope)
{
lock.check();
ServiceLifecycle result = lifecycles.get(scope);
if (result == null)
{
ServiceLifecycleSource source = getService("ServiceLifecycleSource", ServiceLifecycleSource.class);
result = source.get(scope);
}
if (result == null)
throw new RuntimeException(IOCMessages.unknownScope(scope));
return InternalUtils.toServiceLifecycle2(result);
}
@Override
public List<ServiceDecorator> findDecoratorsForService(ServiceDef3 serviceDef)
{
lock.check();
assert serviceDef != null;
Logger logger = getServiceLogger(serviceDef.getServiceId());
Orderer<ServiceDecorator> orderer = new Orderer<ServiceDecorator>(logger, true);
for (Module module : moduleToServiceDefs.keySet())
{
Set<DecoratorDef> decoratorDefs = module.findMatchingDecoratorDefs(serviceDef);
if (decoratorDefs.isEmpty())
continue;
ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
for (DecoratorDef decoratorDef : decoratorDefs)
{
ServiceDecorator decorator = decoratorDef.createDecorator(module, resources);
try
{
orderer.add(decoratorDef.getDecoratorId(), decorator, decoratorDef.getConstraints());
}
catch (IllegalArgumentException e) {
throw new RuntimeException(String.format(
"Service %s has two different decorators methods named decorate%s in different module classes. "
+ "You can solve this by renaming one of them and annotating it with @Match(\"%2$s\").",
serviceDef.getServiceId(), decoratorDef.getDecoratorId()));
}
}
}
return orderer.getOrdered();
}
@Override
public List<ServiceAdvisor> findAdvisorsForService(ServiceDef3 serviceDef)
{
lock.check();
assert serviceDef != null;
Logger logger = getServiceLogger(serviceDef.getServiceId());
Orderer<ServiceAdvisor> orderer = new Orderer<ServiceAdvisor>(logger);
for (Module module : moduleToServiceDefs.keySet())
{
Set<AdvisorDef> advisorDefs = module.findMatchingServiceAdvisors(serviceDef);
if (advisorDefs.isEmpty())
continue;
ServiceResources resources = new ServiceResourcesImpl(this, module, serviceDef, proxyFactory, logger);
for (AdvisorDef advisorDef : advisorDefs)
{
ServiceAdvisor advisor = advisorDef.createAdvisor(module, resources);
orderer.add(advisorDef.getAdvisorId(), advisor, advisorDef.getConstraints());
}
}
return orderer.getOrdered();
}
@Override
public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator,
Module localModule)
{
lock.check();
AnnotationProvider effectiveProvider = annotationProvider != null ? annotationProvider
: new NullAnnotationProvider();
// We do a check here for known marker/type combinations, so that you can use a marker
// annotation
// to inject into a contribution method that contributes to MasterObjectProvider.
// We also force a contribution into MasterObjectProvider to accomplish the same thing.
T result = findServiceByMarkerAndType(objectType, annotationProvider, localModule);
if (result != null)
return result;
MasterObjectProvider masterProvider = getService(IOCConstants.MASTER_OBJECT_PROVIDER_SERVICE_ID,
MasterObjectProvider.class);
return masterProvider.provide(objectType, effectiveProvider, locator, true);
}
private Collection<ServiceDef2> filterByType(Class<?> objectType, Collection<ServiceDef2> serviceDefs)
{
Collection<ServiceDef2> result = CollectionFactory.newSet();
for (ServiceDef2 sd : serviceDefs)
{
if (objectType.isAssignableFrom(sd.getServiceInterface()))
{
result.add(sd);
}
}
return result;
}
@SuppressWarnings("unchecked")
private <T> T findServiceByMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule)
{
if (provider == null)
return null;
Set<ServiceDef2> matches = CollectionFactory.newSet();
List<Class> markers = CollectionFactory.newList();
findServiceDefsMatchingMarkerAndType(objectType, provider, localModule, markers, matches);
// If didn't see @Local or any recognized marker annotation, then don't try to filter that
// way. Continue on, eventually to the MasterObjectProvider service.
if (markers.isEmpty())
{
return null;
}
return extractServiceFromMatches(objectType, markers, matches);
}
/**
* Given markers and matches processed by {@link #findServiceDefsMatchingMarkerAndType(Class, org.apache.tapestry5.ioc.AnnotationProvider, Module, java.util.List, java.util.Set)}, this
* finds the singular match, or reports an error for 0 or 2+ matches.
*/
private <T> T extractServiceFromMatches(Class<T> objectType, List<Class> markers, Set<ServiceDef2> matches)
{
switch (matches.size())
{
case 1:
ServiceDef def = matches.iterator().next();
return getService(def.getServiceId(), objectType);
case 0:
// It's no accident that the user put the marker annotation at the injection
// point, since it matches a known marker annotation, it better be there for
// a reason. So if we don't get a match, we have to assume the user expected
// one, and that is an error.
// This doesn't help when the user places an annotation they *think* is a marker
// but isn't really a marker (because no service is marked by the annotation).
throw new RuntimeException(IOCMessages.noServicesMatchMarker(objectType, markers));
default:
throw new RuntimeException(IOCMessages.manyServicesMatchMarker(objectType, markers, matches));
}
}
private <T> void findServiceDefsMatchingMarkerAndType(Class<T> objectType, AnnotationProvider provider, Module localModule, List<Class> markers,
Set<ServiceDef2> matches)
{
assert provider != null;
boolean localOnly = localModule != null && provider.getAnnotation(Local.class) != null;
matches.addAll(filterByType(objectType, localOnly ? moduleToServiceDefs.get(localModule) : allServiceDefs));
if (localOnly)
{
markers.add(Local.class);
}
for (Class marker : markerToServiceDef.keySet())
{
if (provider.getAnnotation(marker) == null)
{
continue;
}
markers.add(marker);
matches.retainAll(markerToServiceDef.get(marker));
if (matches.isEmpty())
{
return;
}
}
}
@Override
public <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider)
{
return getObject(objectType, annotationProvider, this, null);
}
@Override
public void addRegistryShutdownListener(RegistryShutdownListener listener)
{
lock.check();
registryShutdownHub.addRegistryShutdownListener(listener);
}
@Override
public void addRegistryShutdownListener(Runnable listener)
{
lock.check();
registryShutdownHub.addRegistryShutdownListener(listener);
}
@Override
public void addRegistryWillShutdownListener(Runnable listener)
{
lock.check();
registryShutdownHub.addRegistryWillShutdownListener(listener);
}
@Override
public String expandSymbols(String input)
{
lock.check();
// Again, a bit of work to avoid instantiating the SymbolSource until absolutely necessary.
if (!InternalUtils.containsSymbols(input))
return input;
return getSymbolSource().expandSymbols(input);
}
/**
* Defers obtaining the symbol source until actually needed.
*/
private SymbolSource getSymbolSource()
{
if (symbolSource == null)
symbolSource = getService(SYMBOL_SOURCE_SERVICE_ID, SymbolSource.class);
return symbolSource;
}
@Override
public <T> T autobuild(String description, final Class<T> clazz)
{
return invoke(description, new Invokable<T>()
{
@Override
public T invoke()
{
return autobuild(clazz);
}
});
}
@Override
public <T> T autobuild(final Class<T> clazz)
{
assert clazz != null;
final Constructor constructor = InternalUtils.findAutobuildConstructor(clazz);
if (constructor == null)
{
throw new RuntimeException(IOCMessages.noAutobuildConstructor(clazz));
}
Map<Class, Object> resourcesMap = CollectionFactory.newMap();
resourcesMap.put(OperationTracker.class, RegistryImpl.this);
InjectionResources resources = new MapInjectionResources(resourcesMap);
ObjectCreator<T> plan = InternalUtils.createConstructorConstructionPlan(this, this, resources, null, "Invoking " + proxyFactory.getConstructorLocation(constructor).toString(), constructor);
return plan.createObject();
}
@Override
public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass)
{
return proxy(interfaceClass, implementationClass, this);
}
@Override
public <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass, ObjectLocator locator)
{
assert interfaceClass != null;
assert implementationClass != null;
if (InternalUtils.SERVICE_CLASS_RELOADING_ENABLED && InternalUtils.isLocalFile(implementationClass))
return createReloadingProxy(interfaceClass, implementationClass, locator);
return createNonReloadingProxy(interfaceClass, implementationClass, locator);
}
private <T> T createNonReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
final ObjectLocator locator)
{
final ObjectCreator<T> autobuildCreator = new ObjectCreator<T>()
{
@Override
public T createObject()
{
return locator.autobuild(implementationClass);
}
};
ObjectCreator<T> justInTime = new ObjectCreator<T>()
{
private T delegate;
@Override
public synchronized T createObject()
{
if (delegate == null)
delegate = autobuildCreator.createObject();
return delegate;
}
};
return proxyFactory.createProxy(interfaceClass, justInTime,
String.format("<Autobuild proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
}
private <T> T createReloadingProxy(Class<T> interfaceClass, final Class<? extends T> implementationClass,
ObjectLocator locator)
{
ReloadableObjectCreator creator = new ReloadableObjectCreator(proxyFactory, implementationClass.getClassLoader(),
implementationClass.getName(), loggerSource.getLogger(implementationClass), this, locator);
getService(UpdateListenerHub.class).addUpdateListener(creator);
return proxyFactory.createProxy(interfaceClass, implementationClass, (ObjectCreator<T>) creator,
String.format("<Autoreload proxy %s(%s)>", implementationClass.getName(), interfaceClass.getName()));
}
@Override
public Object provideServiceProxy(String serviceId)
{
return getService(serviceId, Object.class);
}
@Override
public void run(String description, Runnable operation)
{
operationTracker.run(description, operation);
}
@Override
public <T> T invoke(String description, Invokable<T> operation)
{
return operationTracker.invoke(description, operation);
}
@Override
public <T> T perform(String description, IOOperation<T> operation) throws IOException
{
return operationTracker.perform(description, operation);
}
@Override
public Set<Class> getMarkerAnnotations()
{
return markerToServiceDef.keySet();
}
final private static class ModuleComparator implements Comparator<Module> {
@Override
public int compare(Module m1, Module m2)
{
return m1.getLoggerName().compareTo(m2.getLoggerName());
}
}
final static private class DelegatingServiceConfigurationListener implements ServiceConfigurationListener {
final private Logger logger;
private List<ServiceConfigurationListener> delegates;
private Map<ServiceDef, Map> mapped = CollectionFactory.newMap();
private Map<ServiceDef, Collection> unordered = CollectionFactory.newMap();
private Map<ServiceDef, List> ordered = CollectionFactory.newMap();
public DelegatingServiceConfigurationListener(Logger logger)
{
this.logger = logger;
}
public void setDelegates(List<ServiceConfigurationListener> delegates)
{
this.delegates = delegates;
for (ServiceDef serviceDef : mapped.keySet())
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(mapped.get(serviceDef)));
}
}
for (ServiceDef serviceDef : unordered.keySet())
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(unordered.get(serviceDef)));
}
}
for (ServiceDef serviceDef : ordered.keySet())
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(ordered.get(serviceDef)));
}
}
mapped.clear();
mapped = null;
unordered.clear();
unordered = null;
ordered.clear();
ordered = null;
}
@Override
public void onOrderedConfiguration(ServiceDef serviceDef, List configuration)
{
log("ordered", serviceDef, configuration);
if (delegates == null)
{
ordered.put(serviceDef, configuration);
}
else
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onOrderedConfiguration(serviceDef, Collections.unmodifiableList(configuration));
}
}
}
@Override
public void onUnorderedConfiguration(ServiceDef serviceDef, Collection configuration)
{
log("unordered", serviceDef, configuration);
if (delegates == null)
{
unordered.put(serviceDef, configuration);
}
else
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onUnorderedConfiguration(serviceDef, Collections.unmodifiableCollection(configuration));
}
}
}
@Override
public void onMappedConfiguration(ServiceDef serviceDef, Map configuration)
{
log("mapped", serviceDef, configuration);
if (delegates == null)
{
mapped.put(serviceDef, configuration);
}
else
{
for (ServiceConfigurationListener delegate : delegates)
{
delegate.onMappedConfiguration(serviceDef, Collections.unmodifiableMap(configuration));
}
}
}
private void log(String type, ServiceDef serviceDef, Object configuration)
{
if (logger.isDebugEnabled())
{
logger.debug(String.format("Service %s %s configuration: %s",
serviceDef.getServiceId(), type, configuration.toString()));
}
}
}
}