/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.compendium.internal.cm;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.compendium.internal.cm.ManagedFactoryDisposableInvoker.DestructionCodes;
import org.eclipse.gemini.blueprint.context.BundleContextAware;
import org.eclipse.gemini.blueprint.service.exporter.OsgiServiceRegistrationListener;
import org.eclipse.gemini.blueprint.service.exporter.support.DefaultInterfaceDetector;
import org.eclipse.gemini.blueprint.service.exporter.support.ExportContextClassLoaderEnum;
import org.eclipse.gemini.blueprint.service.exporter.support.InterfaceDetector;
import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean;
import org.eclipse.gemini.blueprint.service.exporter.support.ServicePropertiesChangeListener;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.DynamicCollection;
import org.eclipse.gemini.blueprint.util.OsgiServiceUtils;
import org.eclipse.gemini.blueprint.util.internal.MapBasedDictionary;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link FactoryBean Factory} class that automatically manages instances based on the configuration available inside a
* {@link ManagedServiceFactory}.
*
* The factory returns a list of {@link ServiceRegistration} of all published instances.
*
* @author Costin Leau
*/
public class ManagedServiceFactoryFactoryBean implements InitializingBean, BeanClassLoaderAware, BeanFactoryAware,
BundleContextAware, DisposableBean, FactoryBean<Collection> {
/**
* Configuration Admin whiteboard 'listener'.
*
* @author Costin Leau
*/
private class ConfigurationWatcher implements ManagedServiceFactory {
public void deleted(String pid) {
if (log.isTraceEnabled())
log.trace("Configuration [" + pid + "] has been deleted");
destroyInstance(pid);
}
public String getName() {
return "Spring DM managed-service-factory support";
}
public void updated(String pid, Dictionary props) throws ConfigurationException {
if (log.isTraceEnabled())
log.trace("Configuration [" + pid + "] has been updated with properties " + props);
createOrUpdate(pid, new MapBasedDictionary(props));
}
}
/**
* Simple processor that applies the ConfigurationAdmin configuration before the bean is initialized.
*
* @author Costin Leau
*/
private class InitialInjectionProcessor implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
CMUtils.applyMapOntoInstance(bean, initialInjectionProperties, beanFactory);
if (log.isTraceEnabled()) {
log.trace("Applying initial injection for managed bean " + beanName);
}
return bean;
}
}
/**
* Simple associating cache between types and disposable invoker.
*
* @author Costin Leau
*/
private class DestructionInvokerCache {
private final String methodName;
final ConcurrentMap<Class<?>, ManagedFactoryDisposableInvoker> cache =
new ConcurrentHashMap<Class<?>, ManagedFactoryDisposableInvoker>(4);
DestructionInvokerCache(String methodName) {
this.methodName = methodName;
}
ManagedFactoryDisposableInvoker getInvoker(Class<?> type) {
ManagedFactoryDisposableInvoker invoker = cache.get(type);
// non-atomic check for the destruction invoker (duplicate invokers are okay)
if (invoker == null) {
invoker = new ManagedFactoryDisposableInvoker(type, methodName);
cache.put(type, invoker);
}
return invoker;
}
}
/** logger */
private static final Log log = LogFactory.getLog(ManagedServiceFactoryFactoryBean.class);
/** visibility monitor */
private final Object monitor = new Object();
/** Configuration Admin fpid */
private String factoryPid;
/** bundle context */
private BundleContext bundleContext;
/** embedded bean factory for instance management */
private DefaultListableBeanFactory beanFactory;
/** bean definition template */
private RootBeanDefinition templateDefinition;
/** owning bean factory - can be null */
private BeanFactory owningBeanFactory;
/** configuration watcher registration */
private ServiceRegistration configurationWatcher;
/** inner bean service registrations */
private final DynamicCollection serviceRegistrations = new DynamicCollection(8);
/** read-only view of the registration */
private final Collection<ServiceRegistration> userReturnedCollection =
Collections.unmodifiableCollection(serviceRegistrations);
/** lookup map between exporters and associated pids */
private final Map<String, OsgiServiceFactoryBean> serviceExporters =
new ConcurrentHashMap<String, OsgiServiceFactoryBean>(8);
// exporting template
/** listeners */
private OsgiServiceRegistrationListener[] listeners = new OsgiServiceRegistrationListener[0];
/** auto export */
private InterfaceDetector detector = DefaultInterfaceDetector.DISABLED;
/** ccl */
private ExportContextClassLoaderEnum ccl = ExportContextClassLoaderEnum.UNMANAGED;
/** interfaces */
private Class<?>[] interfaces;
/** class loader */
private ClassLoader classLoader;
private boolean autowireOnUpdate = false;
private String updateMethod;
/** update callback */
private UpdateCallback updateCallback;
// this fields gets set whenever a new CM entry is created
// no synch is needed since the CF is guaranteed to be called sequentially and the callback doesn't return
// until the bean is fully initialized and published
public Map initialInjectionProperties;
/**
* destroyed flag - used since some CM implementations still call the service even though it was unregistered
*/
private boolean destroyed = false;
/** service properties */
private volatile Map serviceProperties;
/** special destruction invoker for managed-components/template beans */
private volatile DestructionInvokerCache destructionInvokerFactory;
public void afterPropertiesSet() throws Exception {
synchronized (monitor) {
Assert.notNull(factoryPid, "factoryPid required");
Assert.notNull(bundleContext, "bundleContext is required");
Assert.notNull(templateDefinition, "templateDefinition is required");
Assert.isTrue(!DefaultInterfaceDetector.DISABLED.equals(detector) || !ObjectUtils.isEmpty(interfaces),
"No service interface(s) specified and auto-export "
+ "discovery disabled; change at least one of these properties");
}
processTemplateDefinition();
createEmbeddedBeanFactory();
updateCallback = CMUtils.createCallback(autowireOnUpdate, updateMethod, beanFactory);
registerService();
}
private void processTemplateDefinition() {
// make sure the scope is singleton
templateDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
// let the special invoker handle the destroy method
String destroyMethod = templateDefinition.getDestroyMethodName();
templateDefinition.setDestroyMethodName(null);
// prevent Spring for calling DiposableBean (it's already handled by the invoker)
templateDefinition.registerExternallyManagedDestroyMethod("destroy");
destructionInvokerFactory = new DestructionInvokerCache(destroyMethod);
}
public void destroy() throws Exception {
synchronized (monitor) {
destroyed = true;
// remove factory service
OsgiServiceUtils.unregisterService(configurationWatcher);
configurationWatcher = null;
// destroy instances
destroyFactory();
}
destructionInvokerFactory.cache.clear();
destructionInvokerFactory = null;
synchronized (serviceRegistrations) {
serviceRegistrations.clear();
}
}
private void createEmbeddedBeanFactory() {
synchronized (monitor) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(owningBeanFactory);
if (owningBeanFactory instanceof ConfigurableBeanFactory) {
bf.copyConfigurationFrom((ConfigurableBeanFactory) owningBeanFactory);
}
// just to be on the safe side
bf.setBeanClassLoader(classLoader);
// add autowiring processor
bf.addBeanPostProcessor(new InitialInjectionProcessor());
beanFactory = bf;
}
}
private void registerService() {
synchronized (monitor) {
Dictionary props = new Hashtable(2);
props.put(Constants.SERVICE_PID, factoryPid);
configurationWatcher =
bundleContext.registerService(ManagedServiceFactory.class.getName(), new ConfigurationWatcher(),
props);
}
}
// no monitor since the calling method already holds it
private void destroyFactory() {
if (beanFactory != null) {
// destroy singletons manually to prevent multiple calls of the destruction contract (DisposableBean)
// being called by both the factory and the special invoker
String[] singletonBeans = beanFactory.getSingletonNames();
for (String sigletonName : singletonBeans) {
Object singleton = beanFactory.getBean(sigletonName);
beanFactory.removeBeanDefinition(sigletonName);
ManagedFactoryDisposableInvoker invoker = destructionInvokerFactory.getInvoker(singleton.getClass());
invoker.destroy(sigletonName, singleton, DestructionCodes.BUNDLE_STOPPING);
}
beanFactory = null;
}
}
private void createOrUpdate(String pid, Map props) {
synchronized (monitor) {
if (destroyed)
return;
if (beanFactory.containsBean(pid)) {
updateInstance(pid, props);
} else {
createInstance(pid, props);
}
}
}
private void createInstance(String pid, Map props) {
synchronized (monitor) {
if (destroyed)
return;
beanFactory.registerBeanDefinition(pid, templateDefinition);
initialInjectionProperties = props;
// create instance (causing the injection BPP to be applied)
Object bean = beanFactory.getBean(pid);
registerService(pid, bean);
}
}
private void registerService(String pid, Object bean) {
OsgiServiceFactoryBean exporter = createExporter(pid, bean);
serviceExporters.put(pid, exporter);
try {
serviceRegistrations.add(exporter.getObject());
} catch (Exception ex) {
throw new BeanCreationException("Cannot publish bean for pid " + pid, ex);
}
}
private OsgiServiceFactoryBean createExporter(String beanName, Object bean) {
OsgiServiceFactoryBean exporter = new OsgiServiceFactoryBean();
exporter.setInterfaceDetector(detector);
exporter.setBeanClassLoader(classLoader);
exporter.setBeanName(beanName);
exporter.setBundleContext(bundleContext);
exporter.setExportContextClassLoader(ccl);
exporter.setInterfaces(interfaces);
exporter.setListeners(listeners);
exporter.setTarget(bean);
// add properties
Properties props = new Properties();
if (serviceProperties != null) {
props.putAll(serviceProperties);
}
// add the service pid (to be able to identify the bean instance)
props.put(Constants.SERVICE_PID, beanName);
exporter.setServiceProperties(props);
try {
exporter.afterPropertiesSet();
} catch (Exception ex) {
throw new BeanCreationException("Cannot publish bean for pid " + beanName, ex);
}
return exporter;
}
private void updateInstance(String pid, Map props) {
if (updateCallback != null) {
Object instance = beanFactory.getBean(pid);
updateCallback.update(instance, props);
}
}
private void destroyInstance(String pid) {
synchronized (monitor) {
// bail out fast
if (destroyed)
return;
if (beanFactory.containsBeanDefinition(pid)) {
unregisterService(pid);
Object singleton = beanFactory.getBean(pid);
// remove definition and instance
beanFactory.removeBeanDefinition(pid);
ManagedFactoryDisposableInvoker invoker = destructionInvokerFactory.getInvoker(singleton.getClass());
invoker.destroy(pid, singleton, DestructionCodes.CM_ENTRY_DELETED);
}
}
}
private void unregisterService(String pid) {
OsgiServiceFactoryBean exporterFactory = serviceExporters.remove(pid);
if (exporterFactory != null) {
Object registration = null;
try {
registration = exporterFactory.getObject();
} catch (Exception ex) {
// log the exception and continue
log.error("Could not retrieve registration for pid " + pid, ex);
}
if (log.isTraceEnabled()) {
log.trace("Unpublishing bean for pid " + pid + " w/ registration " + registration);
}
// remove service registration
serviceRegistrations.remove(registration);
exporterFactory.destroy();
}
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
synchronized (monitor) {
this.owningBeanFactory = beanFactory;
}
}
public void setBundleContext(BundleContext bundleContext) {
synchronized (monitor) {
this.bundleContext = bundleContext;
}
}
public Collection getObject() throws Exception {
return userReturnedCollection;
}
public Class<Collection> getObjectType() {
return Collection.class;
}
public boolean isSingleton() {
return true;
}
/**
* Sets the listeners interested in registration and unregistration events.
*
* @param listeners registration/unregistration listeners.
*/
public void setListeners(OsgiServiceRegistrationListener[] listeners) {
if (listeners != null)
this.listeners = listeners;
}
/**
* @param factoryPid The factoryPid to set.
*/
public void setFactoryPid(String factoryPid) {
synchronized (monitor) {
this.factoryPid = factoryPid;
}
}
/**
* @param templateDefinition The templateDefinition to set.
*/
public void setTemplateDefinition(BeanDefinition[] templateDefinition) {
if (templateDefinition != null && templateDefinition.length > 0) {
this.templateDefinition = new RootBeanDefinition();
this.templateDefinition.overrideFrom(templateDefinition[0]);
} else {
this.templateDefinition = null;
}
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setInterfaceDetector(InterfaceDetector detector) {
this.detector = detector;
}
/**
* @param ccl The ccl to set.
*/
public void setExportContextClassLoader(ExportContextClassLoaderEnum ccl) {
this.ccl = ccl;
}
/**
* @param interfaces The interfaces to set.
*/
public void setInterfaces(Class<?>[] interfaces) {
this.interfaces = interfaces;
}
/**
* Sets whether autowire on update should be performed automatically or not.
*
* @param autowireOnUpdate
*/
public void setAutowireOnUpdate(boolean autowireOnUpdate) {
this.autowireOnUpdate = autowireOnUpdate;
}
/**
* @param updateMethod The updateMethod to set.
*/
public void setUpdateMethod(String updateMethod) {
this.updateMethod = updateMethod;
}
/**
* Sets the properties used when exposing the target as an OSGi service. If the given argument implements (
* {@link ServicePropertiesChangeListener}), any updates to the properties will be reflected by the service
* registration.
*
* @param serviceProperties properties used for exporting the target as an OSGi service
*/
public void setServiceProperties(Map serviceProperties) {
this.serviceProperties = serviceProperties;
}
}