/*
* $Id$
*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.system;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import ome.api.ServiceInterface;
import ome.conditions.ApiUsageException;
import ome.util.messages.InternalMessage;
import ome.util.messages.MessageException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
/**
* Provides static access for the creation of singleton and non-singleton
* application contexts. Also provides context names as constant fields which
* can be used for the lookup of particular contexts, through either
* {@link #getInstance(String)} or
* {@link ome.system.ServiceFactory#ServiceFactory(String)}.
*
* By passing a {@link java.util.Properties} instance into the
* {@link #getClientContext(Properties)} method, a non-static version is
* created. Currently this is only supported for the client context.
*
* @author <br>
* Josh Moore <a
* href="mailto:josh.moore@gmx.de"> josh.moore@gmx.de</a>
* @since OME3.0
*/
public class OmeroContext extends ClassPathXmlApplicationContext {
/**
* identifier for an OmeroContext configured in
* classpath*:beanRefContext.xml for use by remote (via JNDI/RMI) clients.
*/
public final static String CLIENT_CONTEXT = "ome.client";
/**
* identifier for an OmeroContext configured in
* classpath*:beanRefContext.xml for use by server-side processes. All
* objects obtained from the context are in a state for immediate use.
*/
public final static String MANAGED_CONTEXT = "ome.server";
private static OmeroContext _client;
private static OmeroContext _managed;;
/**
* Multicaster used by this instance. Unlike other Spring application
* contexts, this context assumes that a "global multicaster" is configured
* and so does not pass messages to the parent context, but assumes that
* the multicaster will do this. In order to implement this logic, it is
* necessary to lookup the entity by name, since all getters and fields
* are private in the superclasses.
*/
private ApplicationEventMulticaster multicaster;
// ~ Constructors
// =========================================================================
public OmeroContext(String configLocation) throws BeansException {
super(configLocation);
}
public OmeroContext(String[] configLocations) throws BeansException {
super(configLocations);
}
public OmeroContext(String[] configLocations, boolean refresh)
throws BeansException {
super(configLocations, refresh);
}
public OmeroContext(String[] configLocations, ApplicationContext parent)
throws BeansException {
super(configLocations, parent);
}
public OmeroContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {
super(configLocations, refresh, parent);
}
// ~ Creation
// =========================================================================
private final static Object mutex = new Object();
/**
* create (if necessary) and return the single default client OmeroContext.
* Any two calls to this method will return the same (==) context instance.
*
* @see #CLIENT_CONTEXT
*/
public static OmeroContext getClientContext() {
synchronized (mutex) {
if (_client == null) {
_client = getInstance(CLIENT_CONTEXT);
}
return _client;
}
}
/**
* initialize a new client OmeroContext (named {@link #CLIENT_CONTEXT}),
* using the {@link #getContext(Properties, String)} method.
*
* @see #getContext(Properties, String)
* @see #CLIENT_CONTEXT
* @see ServiceFactory#ServiceFactory(Login)
* @see ServiceFactory#ServiceFactory(Server)
* @see ServiceFactory#ServiceFactory(Properties)
*/
public static OmeroContext getClientContext(Properties props) {
return getContext(props, CLIENT_CONTEXT);
}
/**
* initialize a new client OmeroContext using the {@link Properties}
* provided as values for property (e.g. ${name}) replacement in Spring. Two
* calls to this method with the same argument will return different ( =! )
* contexts.
*
* @param props
* Non-null properties for replacement.
* @param context
* Non-null name of context to find in beanRefContext.xml
*
* @see ServiceFactory#ServiceFactory(Login)
* @see ServiceFactory#ServiceFactory(Server)
* @see ServiceFactory#ServiceFactory(Properties)
*/
public static OmeroContext getContext(Properties props, String context) {
if (props == null || context == null) {
throw new ApiUsageException("Arguments may not be null.");
}
Properties copy = new Properties(props);
ConstructorArgumentValues ctorArg = new ConstructorArgumentValues();
ctorArg.addGenericArgumentValue(copy);
BeanDefinition definition = new RootBeanDefinition(Properties.class,
ctorArg, null);
StaticApplicationContext staticContext = new StaticApplicationContext();
staticContext.registerBeanDefinition("properties", definition);
staticContext.refresh();
OmeroContext ctx = new Locator().lookup(context, staticContext);
return ctx;
}
/**
* create (if necessary) and return the single default managed OmeroContext.
* Any two calls to this method will return the same (==) context instance.
* Managed means that the services are fully wrapped by interceptors, and
* are essentially the services made available remotely.
*
* @see #MANAGED_CONTEXT
*/
public static OmeroContext getManagedServerContext() {
synchronized (mutex) {
if (_managed == null) {
_managed = getInstance(MANAGED_CONTEXT);
}
return _managed;
}
}
/**
* create (if necessary) and return the single default OmeroContext named by
* the beanFactoryName parameter. Any two calls to this method with the same
* parameter will return the same (==) context instance.
*
* @see #getClientContext()
* @see #getManagedServerContext()
*/
public static OmeroContext getInstance(String beanFactoryName) {
OmeroContext ctx = (OmeroContext) ContextSingletonBeanFactoryLocator
.getInstance().useBeanFactory(beanFactoryName).getFactory();
try {
ctx.getBeanFactory();
} catch (IllegalStateException ise) {
// Here we catch IllegalStateException because it implies that
// the bean factory isn't created yet. However, if that goes
// wrong, we need to rollback.
try {
ctx.refresh();
} catch (RuntimeException re) {
if (ctx != null) {
try {
ctx.close();
} catch (Exception e) {
// OK ignoring to rethrow the original exception
}
}
throw re;
}
}
return ctx;
}
// ~ Utilities
// =========================================================================
/**
* Uses the methods of this context's {@link BeanFactory} to autowire any
* Object based on the given beanName.
*
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#applyBeanPropertyValues(java.lang.Object,
* java.lang.String)
*/
public void applyBeanPropertyValues(Object target, String beanName) {
this.getAutowireCapableBeanFactory().applyBeanPropertyValues(target,
beanName);
}
/**
* Uses the methods of this context's {@link BeanFactory} to autowire any
* Object based on the service class. This is used by
* {@link SelfConfigurableService} instances to acquire dependencies.
*
* @see SelfConfigurableService
* @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#applyBeanPropertyValues(java.lang.Object,
* java.lang.String)
*/
public void applyBeanPropertyValues(Object target,
Class<? extends ServiceInterface> beanInterface) {
// TODO: it would be better to have this as <? extends
// SelfConfigurableService>
// but there are issues because of the ApplicationContextAware. Perhaps
// we can combine them later.
applyBeanPropertyValues(target, "internal-" + beanInterface.getName());
}
/**
* refreshes all the nested OmeroContexts within this instance. This is
* useful when using a static context, and {@link Properties} which were
* pulled from {@link System#getProperties()} have been changed.
*
* If this is a server-side instance ({@link #MANAGED_CONTEXT}), this may
* take a significant amount of time.
*
* @see org.springframework.context.ConfigurableApplicationContext#refresh()
*/
public void refreshAll() {
ApplicationContext ac = this;
List<ConfigurableApplicationContext> list = new LinkedList<ConfigurableApplicationContext>();
while (ac instanceof ConfigurableApplicationContext) {
list.add((ConfigurableApplicationContext) ac);
ac = ac.getParent();
}
for (int i = list.size() - 1; i >= 0; i--) {
list.get(i).refresh();
}
}
/**
* Calls {@link #refreshAll()} if {@link #isRunning()} throws an
* {@link IllegalStateException}.
*/
public void refreshAllIfNecessary() {
try {
isRunning();
} catch (IllegalStateException ise) {
refreshAll();
}
}
/**
* closes all the nested OmeroContexts within this instance.
*
* If this is a server-side instance ({@link #MANAGED_CONTEXT}), this may
* take a significant amount of time.
*
* @see org.springframework.context.ConfigurableApplicationContext#close()
*/
public void closeAll() {
ApplicationContext ac = this;
List<ConfigurableApplicationContext> list = new LinkedList<ConfigurableApplicationContext>();
while (ac instanceof ConfigurableApplicationContext) {
list.add((ConfigurableApplicationContext) ac);
ac = ac.getParent();
}
for (int i = 0; i < list.size(); i++) {
list.get(i).close();
}
}
public String getProperty(String propertyName) {
PreferenceContext pc = (PreferenceContext) getBean("preferenceContext");
return pc.getProperty(propertyName);
}
@Override
public void publishEvent(ApplicationEvent event) {
multicaster.multicastEvent(event);
}
/**
* Convenience method around
* {@link #publishEvent(org.springframework.context.ApplicationEvent)} which
* catches all {@link MessageException} and unwraps the contained
* {@link Throwable} instance and rethrows.
*
* @param msg
* @throws Throwable
*/
public void publishMessage(InternalMessage msg) throws Throwable {
try {
publishEvent(msg);
} catch (MessageException me) {
throw me.getException();
}
}
@Override
protected void onRefresh() throws BeansException {
super.onRefresh();
multicaster = (ApplicationEventMulticaster) getBean("applicationEventMulticaster");
}
// ~ Non-singleton locator
// =========================================================================
/**
* provides access to the protected methods of
* {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}
* which cannot be used externally.
*/
protected static class Locator extends ContextSingletonBeanFactoryLocator {
// copied from ContextSingletonBeanFactoryLocator
private static final String BEANS_REFS_XML_NAME = "classpath*:beanRefContext.xml";
public Locator() {
super(null);
}
/**
* uses
* {@link ContextSingletonBeanFactoryLocator#createDefinition(java.lang.String, java.lang.String)}
* and
* {@link ContextSingletonBeanFactoryLocator#initializeDefinition(org.springframework.beans.factory.BeanFactory)}
* to create a new context from a given definition.
*/
public OmeroContext lookup(String selector, ApplicationContext parent) {
ConfigurableApplicationContext beanRefContext = (ConfigurableApplicationContext) createDefinition(
BEANS_REFS_XML_NAME, "manual");
initializeDefinition(beanRefContext);
BeanDefinition definition = beanRefContext.getBeanFactory()
.getBeanDefinition(selector);
ValueHolder holder = definition.getConstructorArgumentValues()
.getGenericArgumentValue(List.class);
List<TypedStringValue> files = (List<TypedStringValue>) holder
.getValue();
List<String> fileStrings = new ArrayList<String>(files.size());
for (TypedStringValue tsv : files) {
fileStrings.add(tsv.getValue());
}
OmeroContext c = new OmeroContext(fileStrings
.toArray(new String[0]), true, parent);
return c;
}
}
}