
org.specrunner.SRServices Maven / Gradle / Ivy
/*
SpecRunner - Acceptance Test Driven Development Tool
Copyright (C) 2011-2014 Thiago Santos
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see
*/
package org.specrunner;
import java.util.HashMap;
import java.util.Map;
import org.specrunner.comparators.IComparatorManager;
import org.specrunner.converters.IConverterManager;
import org.specrunner.core.SpecRunnerPipelineUtils;
import org.specrunner.expressions.IExpressionFactory;
import org.specrunner.features.IFeatureManager;
import org.specrunner.pipeline.IChannel;
import org.specrunner.pipeline.IChannelFactory;
import org.specrunner.pipeline.IPipeline;
import org.specrunner.pipeline.PipelineException;
import org.specrunner.readers.IReaderManager;
import org.specrunner.util.UtilLog;
/**
* Centralizes the services provided by the SpecRunner framework. To get full
* list of available services list use method getServices()
. For
* specific services, i.e. if you need an instance of IStringAlignerFactory, use
* SRServices.get(IStringAlignerFactory.class)
.
*
*
* This is thread-safe!
*
* @author Thiago Santos
*
*/
public final class SRServices {
/**
* Set if SRServices is thread-safe or not.
*/
private static boolean threadSafe = Boolean.valueOf(System.getProperty("sr.threadsafe", "true"));
/**
* Instance global.
*/
private static SRServices global;
/**
* Instance by thread.
*/
private static ThreadLocal instance = new ThreadLocal();
/**
* Map of services by type.
*/
private final Map, Object> servicePool = new HashMap, Object>();
/**
* Configuration.
*/
private ISRMapping mapping;
/**
* Thread name.
*/
private String threadName;
/**
* Create a group of services provided by SpecRunner.
*/
private SRServices() {
String str = System.getProperty("sr.mapping", "org.specrunner.core.SRMappingDefault");
try {
mapping = (ISRMapping) Class.forName(str).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Gets the default instance of a given services.
*
* @param
* The class type.
* @param type
* The type.
* @return The service object.
*/
private T getDefault(Class type) {
Object result = null;
if (type == SRServices.class) {
result = this;
} else {
result = mapping.getDefault(type);
}
return type.cast(result);
}
/**
* Bind a service to the SpecRunner. Be sure you create an instance of a
* given service to each thread. This is quite simple, for example, in JUnit
* testing set features/services using @Before
annotation
* instead of @BeforeClass
. Features, however, can be set on
* @BeforeClass
without side-effects.
*
* @param
* The type to be bound.
* @param clazz
* The service type.
* @param instance
* The service instance.
*/
public void bind(Class clazz, T instance) {
servicePool.put(clazz, instance);
}
/**
* Lookup for a service by its type.
*
* @param
* The service type.
* @param clazz
* The service type.
* @return The instance of the given service, or throws
* {@link IllegalArgumentException} if the service was not
* previously bound.
*/
@SuppressWarnings("unchecked")
public T lookup(Class clazz) {
Object obj = servicePool.get(clazz);
if (obj == null) {
obj = getDefault(clazz);
if (obj != null) {
bind(clazz, (T) obj);
}
}
if (obj == null) {
throw new IllegalArgumentException("Object of type '" + clazz + "' not found in pool of services.");
}
return (T) obj;
}
/**
* The map of all available services. This is the mapping itself, not a
* copy, is up to you change as you want (take your chances).
*
* @return The services mapping.
*/
public Map, Object> getServices() {
return servicePool;
}
/**
* Get the thread associated to this service..
*
* @return A thread name.
*/
public String getThreadName() {
return threadName;
}
/**
* Shortcut static method to recover a service (thread-safe).
*
* @param
* A class type.
* @param clazz
* see {@link lookup}.
* @return see {@link lookup}.
*/
public static T get(Class clazz) {
return get().lookup(clazz);
}
/**
* Gets the instance of {@link SRServices}.
*
* @return The services instance.
*/
public static SRServices get() {
if (threadSafe) {
if (instance.get() == null) {
SRServices service = new SRServices();
addHook(service);
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug("Instance is thread-safe: " + service);
}
instance.set(service);
}
return instance.get();
} else {
if (global == null) {
global = new SRServices();
addHook(global);
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug("Instance is global: " + global);
}
}
return global;
}
}
/**
* Add shutdown hook to the environment.
*
* @param service
* A service to be shut down.
*/
protected static void addHook(SRServices service) {
service.threadName = Thread.currentThread().getName();
Runtime.getRuntime().addShutdownHook(new ShutDown(service));
}
/**
* Shortcut method to feature manager.
*
* @return The feature manager.
*/
public static IFeatureManager getFeatureManager() {
return get(IFeatureManager.class);
}
/**
* Shortcut method to reader manager.
*
* @return The reader manager.
*/
public static IReaderManager getReaderManager() {
return get(IReaderManager.class);
}
/**
* Shortcut method to converter manager.
*
* @return The converter manager.
*/
public static IConverterManager getConverterManager() {
return get(IConverterManager.class);
}
/**
* Shortcut method to comparator manager.
*
* @return The feature comparator.
*/
public static IComparatorManager getComparatorManager() {
return get(IComparatorManager.class);
}
/**
* Shortcut method to expression factory.
*
* @return The expression factory.
*/
public static IExpressionFactory getExpressionFactory() {
return get(IExpressionFactory.class);
}
/**
* Shortcut method to the topmost interface.
*
* @return A specification runner.
* @throws SpecRunnerException
* On recovering errors.
*/
public static ISpecRunner getSpecRunner() throws SpecRunnerException {
return get(ISpecRunnerFactory.class).newRunner();
}
/**
* Shortcut method to the topmost interface for programmatic plugins.
*
* @return A specification runner.
* @throws SpecRunnerException
* On recovering errors.
*/
public static ISpecRunnerPlugin getSpecRunnerPlugin() throws SpecRunnerException {
return get(ISpecRunnerFactoryPlugin.class).newRunner();
}
/**
* Release all reusable resources pending.
*/
public static void release() {
if (UtilLog.LOG.isInfoEnabled()) {
UtilLog.LOG.info("Release programmatic call.");
}
release(instance.get());
}
/**
* Release reusable resources.
*
* @param service
* The service.
*/
private static void release(SRServices service) {
try {
IChannel channel = service.lookup(IChannelFactory.class).newChannel();
channel.add(ShutDown.SHUTDOWN, service);
IPipeline pipeline = SpecRunnerPipelineUtils.getPipeline(service, "specrunner_shutdown.xml");
pipeline.process(channel);
} catch (Exception e) {
if (UtilLog.LOG.isDebugEnabled()) {
UtilLog.LOG.debug(e.getMessage(), e);
}
}
}
/**
* Shutdown hook thread.
*
* @author Thiago Santos.
*
*/
public static class ShutDown extends Thread {
/**
* Name of service in shutdown channel.
*/
public static final String SHUTDOWN = "shutdown";
/**
* The services instance to be shutdown.
*/
private final SRServices shutdown;
/**
* The services instance to shutdown on System.exit(...)
.
*
* @param shutdown
* The instance.
*/
public ShutDown(SRServices shutdown) {
this.shutdown = shutdown;
}
@Override
public void run() {
if (UtilLog.LOG.isInfoEnabled()) {
UtilLog.LOG.info("Release shutdown call.");
}
SRServices.release(shutdown);
}
/**
* Return the services instance bound on channel.
*
* @param channel
* The channel.
* @return The service instance.
* @throws PipelineException
* On lookup errors.
*/
public static SRServices recover(IChannel channel) throws PipelineException {
return channel.get(SHUTDOWN, SRServices.class);
}
}
}