org.eclipse.gemini.blueprint.service.dependency.internal.DefaultMandatoryDependencyManager.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gemini.blueprint.service.dependency.internal.DefaultMandatoryDependencyManager.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.service.dependency.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.service.exporter.support.internal.controller.ExporterControllerUtils;
import org.eclipse.gemini.blueprint.service.exporter.support.internal.controller.ExporterInternalActions;
import org.eclipse.gemini.blueprint.service.importer.OsgiServiceDependency;
import org.eclipse.gemini.blueprint.service.importer.support.AbstractOsgiServiceImportFactoryBean;
import org.eclipse.gemini.blueprint.service.importer.support.Availability;
import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceCollectionProxyFactoryBean;
import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean;
import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterControllerUtils;
import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterInternalActions;
import org.eclipse.gemini.blueprint.service.importer.support.internal.dependency.ImporterStateListener;
import org.eclipse.gemini.blueprint.util.internal.BeanFactoryUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Default implementation of {@link MandatoryServiceDependencyManager} which determines the relationship between
 * importers and exporters and unpublishes exported service if they dependent, transitively, on imported OSGi services
 * that are mandatory and cannot be satisfied.
 * 
 * <strong>Note:</strong> aimed for singleton beans only
 * 
 * @author Costin Leau
 * 
 */
public class DefaultMandatoryDependencyManager
        implements MandatoryServiceDependencyManager, BeanFactoryAware, DisposableBean {

    /**
     * Importer state listener that gets associated with each exporter.
     * 
     * @author Costin Leau
     */
    private class ImporterDependencyListener implements ImporterStateListener {

        private final Object exporter;
        private final String exporterName;

        private ImporterDependencyListener(Object exporter) {
            this.exporter = exporter;
            this.exporterName = (String) exporterToName.get(exporter);

        }

        public void importerSatisfied(Object importer, OsgiServiceDependency dependency) {

            boolean trace = log.isTraceEnabled();
            boolean exporterRemoved = false;

            // update importer status
            synchronized (exporter) {
                Map<Object, Boolean> importers = exporterToImporterDeps.get(exporter);
                exporterRemoved = !(importers != null);

                // if the list is not present (exporter was removed), bail out
                if (!exporterRemoved) {
                    importers.put(importer, Boolean.TRUE);
                    if (trace)
                        log.trace("Importer [" + importerToName.get(importer)
                                + "] is satisfied; checking the rest of the dependencies for exporter "
                                + exporterToName.get(exporter));

                    checkIfExporterShouldStart(exporter, importers);
                }
            }

            if (exporterRemoved && trace)
                log.trace("Exporter [" + exporterName + "] removed; ignoring dependency ["
                        + dependency.getBeanName() + "] update");
        }

        public void importerUnsatisfied(Object importer, OsgiServiceDependency dependency) {

            boolean exporterRemoved = false;

            synchronized (exporter) {
                Map<Object, Boolean> importers = exporterToImporterDeps.get(exporter);
                exporterRemoved = !(importers != null);
                if (!exporterRemoved) {
                    // record the importer status
                    importers.put(importer, Boolean.FALSE);
                }
            }

            boolean trace = log.isTraceEnabled();

            if (!exporterRemoved) {
                if (trace)
                    log.trace("Exporter [" + exporterName + "] stopped; transitive OSGi dependency ["
                            + dependency.getBeanName() + "] is unsatifised");

                // if the importer goes down, simply shut down the exporter
                stopExporter(exporter);
            } else {
                if (trace) {
                    log.trace("Exporter [" + exporterName + "] removed; ignoring dependency ["
                            + dependency.getBeanName() + "] update");
                }
            }
        }
    }

    private static final Log log = LogFactory.getLog(DefaultMandatoryDependencyManager.class);

    /** cache map - useful for avoiding double registration */
    private final ConcurrentMap<String, Object> exportersSeen = new ConcurrentHashMap<String, Object>(4);

    private static final Object VALUE = new Object();

    /**
     * Importers on which an exporter depends. The exporter instance is used as a key, while the value is represented by
     * a list of importers name and their status (up or down).
     */
    private final Map<Object, Map<Object, Boolean>> exporterToImporterDeps = new ConcurrentHashMap<Object, Map<Object, Boolean>>(
            8);

    /** exporter -> importer listener map */
    private final Map<Object, ImporterStateListener> exporterListener = new ConcurrentHashMap<Object, ImporterStateListener>(
            8);

    /** importer -> name map */
    private final ConcurrentMap<Object, String> importerToName = new ConcurrentHashMap<Object, String>(8);

    /** exporter name map */
    private final Map<Object, String> exporterToName = new ConcurrentHashMap<Object, String>(8);

    /** owning bean factory */
    private ConfigurableListableBeanFactory beanFactory;

    public void addServiceExporter(Object exporter, String exporterBeanName) {
        Assert.hasText(exporterBeanName);

        if (exportersSeen.putIfAbsent(exporterBeanName, VALUE) == null) {

            String beanName = exporterBeanName;

            if (beanFactory.isFactoryBean(exporterBeanName))
                beanName = BeanFactory.FACTORY_BEAN_PREFIX + exporterBeanName;

            // check if it's factory bean (no need to check for abstract
            // definition since we're called by a BPP)
            if (!beanFactory.isSingleton(beanName)) {
                log.info("Exporter [" + beanName + "] is not singleton and will not be tracked");
            }

            else {
                if (log.isDebugEnabled())
                    log.debug("Exporter [" + beanName + "] is being tracked for dependencies");

                exporterToName.put(exporter, exporterBeanName);
                // retrieve associated controller
                ExporterInternalActions controller = ExporterControllerUtils.getControllerFor(exporter);

                // disable publication at startup
                controller.registerServiceAtStartup(false);

                // populate the dependency maps
                discoverDependentImporterFor(exporterBeanName, exporter);
            }
        }
    }

    /**
     * Discover all the importers for the given exporter. Since the importers are already created before the exporter
     * instance is created, this method only does filtering based on the mandatory imports.
     */
    protected void discoverDependentImporterFor(String exporterBeanName, Object exporter) {

        boolean trace = log.isTraceEnabled();

        // determine exporters
        String[] importerA = BeanFactoryUtils.getTransitiveDependenciesForBean(beanFactory, exporterBeanName, true,
                OsgiServiceProxyFactoryBean.class);

        String[] importerB = BeanFactoryUtils.getTransitiveDependenciesForBean(beanFactory, exporterBeanName, true,
                OsgiServiceCollectionProxyFactoryBean.class);

        String[] importerNames = StringUtils.concatenateStringArrays(importerA, importerB);

        // create map of associated importers
        Map<Object, String> dependingImporters = new LinkedHashMap<Object, String>(importerNames.length);

        if (trace)
            log.trace("Exporter [" + exporterBeanName + "] depends (transitively) on the following importers:"
                    + ObjectUtils.nullSafeToString(importerNames));

        // first create a listener for the exporter
        ImporterStateListener listener = new ImporterDependencyListener(exporter);
        exporterListener.put(exporter, listener);

        // exclude non-mandatory importers
        // non-singletons get added only once (as one instance is enough)
        for (int i = 0; i < importerNames.length; i++) {
            if (beanFactory.isSingleton(importerNames[i])) {
                Object importer = beanFactory.getBean(importerNames[i]);

                // create an importer -> exporter association
                if (isMandatory(importer)) {
                    dependingImporters.put(importer, importerNames[i]);
                    importerToName.putIfAbsent(importer, importerNames[i]);
                }

                else if (trace)
                    log.trace("Importer [" + importerNames[i] + "] is optional; skipping it");
            } else if (trace)
                log.trace("Importer [" + importerNames[i] + "] is a non-singleton; ignoring it");
        }

        if (trace)
            log.trace("After filtering, exporter [" + exporterBeanName + "] depends on importers:"
                    + dependingImporters.values());

        Collection<Object> filteredImporters = dependingImporters.keySet();

        // add the importers and their status to the collection
        synchronized (exporter) {
            Map<Object, Boolean> importerStatuses = new LinkedHashMap<Object, Boolean>(filteredImporters.size());

            for (Iterator<Object> iter = filteredImporters.iterator(); iter.hasNext();) {
                Object importer = iter.next();
                importerStatuses.put(importer, Boolean.valueOf(isSatisfied(importer)));
                // add the listener after the importer status has been recorded
                addListener(importer, listener);
            }
            exporterToImporterDeps.put(exporter, importerStatuses);
            if (!checkIfExporterShouldStart(exporter, importerStatuses)) {
                callUnregisterOnStartup(exporter);
            }
        }
    }

    private boolean checkIfExporterShouldStart(Object exporter, Map<Object, Boolean> importers) {

        if (!importers.containsValue(Boolean.FALSE)) {
            startExporter(exporter);

            if (log.isDebugEnabled())
                log.trace("Exporter [" + exporterToName.get(exporter) + "] started; "
                        + "all its dependencies are satisfied");
            return true;

        } else {
            List<String> unsatisfiedDependencies = new ArrayList<String>(importers.size());

            for (Iterator<Map.Entry<Object, Boolean>> iterator = importers.entrySet().iterator(); iterator
                    .hasNext();) {
                Map.Entry<Object, Boolean> entry = iterator.next();
                if (Boolean.FALSE.equals(entry.getValue()))
                    unsatisfiedDependencies.add(importerToName.get(entry.getKey()));
            }

            if (log.isTraceEnabled()) {
                log.trace("Exporter [" + exporterToName.get(exporter)
                        + "] not started; there are still unsatisfied dependencies " + unsatisfiedDependencies);
            }

            return false;
        }
    }

    public void removeServiceExporter(Object bean, String beanName) {
        if (log.isTraceEnabled()) {
            log.trace("Removing exporter [" + beanName + "]");
        }

        // remove the exporter and its listeners from the map
        ImporterStateListener stateListener = (ImporterStateListener) exporterListener.remove(bean);

        Map<Object, Boolean> importers;

        synchronized (bean) {
            importers = exporterToImporterDeps.remove(bean);
        }

        // no need to do synchronization anymore since no other threads will find the collection
        if (importers != null)
            for (Iterator<Object> iterator = importers.keySet().iterator(); iterator.hasNext();) {
                Object importer = iterator.next();
                // get associated controller
                removeListener(importer, stateListener);
            }
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    public void destroy() {
        exportersSeen.clear();
        exporterListener.clear();
        exporterToImporterDeps.clear();
        exporterToName.clear();
        importerToName.clear();
    }

    private void startExporter(Object exporter) {
        ExporterControllerUtils.getControllerFor(exporter).registerService();
    }

    private void stopExporter(Object exporter) {
        ExporterControllerUtils.getControllerFor(exporter).unregisterService();
    }

    private void callUnregisterOnStartup(Object exporter) {
        ExporterControllerUtils.getControllerFor(exporter).callUnregisterOnStartup();
    }

    private void addListener(Object importer, ImporterStateListener stateListener) {
        ImporterInternalActions controller = ImporterControllerUtils.getControllerFor(importer);
        controller.addStateListener(stateListener);
    }

    private void removeListener(Object importer, ImporterStateListener stateListener) {
        ImporterInternalActions controller = ImporterControllerUtils.getControllerFor(importer);
        controller.removeStateListener(stateListener);
    }

    private boolean isSatisfied(Object importer) {
        return ImporterControllerUtils.getControllerFor(importer).isSatisfied();
    }

    private boolean isMandatory(Object importer) {
        if (importer instanceof AbstractOsgiServiceImportFactoryBean) {
            return Availability.MANDATORY
                    .equals(((AbstractOsgiServiceImportFactoryBean) importer).getAvailability());
        }
        return false;
    }
}