org.eclipse.gemini.blueprint.compendium.internal.cm.ManagedServiceFactoryFactoryBean.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gemini.blueprint.compendium.internal.cm.ManagedServiceFactoryFactoryBean.java

Source

/******************************************************************************
 * 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;
    }
}