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