/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc., Oracle 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.
* Oracle Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.extender.internal.dependencies.startup;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEvent;
import org.eclipse.gemini.blueprint.extender.OsgiServiceDependencyFactory;
import org.eclipse.gemini.blueprint.extender.event.BootstrappingDependenciesEvent;
import org.eclipse.gemini.blueprint.extender.event.BootstrappingDependencyEvent;
import org.eclipse.gemini.blueprint.extender.internal.util.PrivilegedUtils;
import org.eclipse.gemini.blueprint.service.importer.OsgiServiceDependency;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyEvent;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitEndedEvent;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyWaitStartingEvent;
import org.eclipse.gemini.blueprint.util.OsgiFilterUtils;
import org.eclipse.gemini.blueprint.util.OsgiListenerUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
/**
* ServiceListener used for tracking dependent services. Even if the ServiceListener receives event synchronously,
* mutable properties should be synchronized to guarantee safe publishing between threads.
*
* @author Costin Leau
* @author Hal Hildebrand
* @author Andy Piper
*/
public class DependencyServiceManager {
private static final Log log = LogFactory.getLog(DependencyServiceManager.class);
protected final Map<MandatoryServiceDependency, String> dependencies =
Collections.synchronizedMap(new LinkedHashMap<MandatoryServiceDependency, String>());
protected final Map<MandatoryServiceDependency, String> unsatisfiedDependencies =
Collections.synchronizedMap(new LinkedHashMap<MandatoryServiceDependency, String>());
private final ContextExecutorAccessor contextStateAccessor;
private final BundleContext bundleContext;
private final ServiceListener listener;
private final DelegatedExecutionOsgiBundleApplicationContext context;
/**
* Task to execute if all dependencies are met.
*/
private final Runnable executeIfDone;
/** Maximum waiting time used in events when waiting for dependencies */
private final long waitTime;
/** dependency factories */
private List<OsgiServiceDependencyFactory> dependencyFactories;
/**
* Actual ServiceListener.
*
* @author Costin Leau
* @author Hal Hildebrand
*/
private class DependencyServiceListener implements ServiceListener {
/**
* Process serviceChanged events, completing context initialization if all the required dependencies are
* satisfied.
*
* @param serviceEvent
*/
public void serviceChanged(ServiceEvent serviceEvent) {
boolean trace = log.isTraceEnabled();
try {
if (unsatisfiedDependencies.isEmpty()) {
// already completed but likely called due to threading
if (trace) {
log.trace("Handling service event, but no unsatisfied dependencies exist for "
+ context.getDisplayName());
}
return;
}
ServiceReference ref = serviceEvent.getServiceReference();
if (trace) {
log.trace("Handling service event [" + OsgiStringUtils.nullSafeToString(serviceEvent) + ":"
+ OsgiStringUtils.nullSafeToString(ref) + "] for " + context.getDisplayName());
}
updateDependencies(serviceEvent);
ContextState state = contextStateAccessor.getContextState();
// already resolved (closed or timed-out)
if (state.isResolved()) {
deregister();
return;
}
// Good to go!
if (unsatisfiedDependencies.isEmpty()) {
deregister();
// context.listener = null;
log.info("No unsatisfied OSGi service dependencies; completing initialization for "
+ context.getDisplayName());
// execute task to complete initialization
// NOTE: the runnable should be able to delegate any long
// process to a
// different thread.
executeIfDone.run();
}
} catch (Throwable th) {
// frameworks will simply not log exception for event handlers
log.error("Exception during dependency processing for " + context.getDisplayName(), th);
contextStateAccessor.fail(th);
}
}
private void updateDependencies(ServiceEvent serviceEvent) {
boolean trace = log.isTraceEnabled();
boolean debug = log.isDebugEnabled();
String referenceToString = null;
String contextToString = null;
if (debug) {
referenceToString = OsgiStringUtils.nullSafeToString(serviceEvent.getServiceReference());
contextToString = context.getDisplayName();
}
for (MandatoryServiceDependency dependency : dependencies.keySet()) {
// check all dependencies (there might be multiple imports for the same service)
if (dependency.matches(serviceEvent)) {
if (trace) {
log.trace(dependency + " matched: " + referenceToString);
}
switch (serviceEvent.getType()) {
case ServiceEvent.REGISTERED:
case ServiceEvent.MODIFIED:
dependency.increment();
if (unsatisfiedDependencies.remove(dependency) != null) {
if (debug) {
log.debug("Registered dependency for " + contextToString + "; eliminating "
+ dependency + ", remaining [" + unsatisfiedDependencies + "]");
}
sendDependencySatisfiedEvent(dependency);
sendBootstrappingDependenciesEvent(unsatisfiedDependencies.keySet());
} else {
if (debug) {
log.debug("Increasing the number of matching services for " + contextToString + "; "
+ dependency + ", remaining [" + unsatisfiedDependencies + "]");
}
}
break;
case ServiceEvent.UNREGISTERING:
int count = dependency.decrement();
if (count == 0) {
unsatisfiedDependencies.put(dependency, dependency.getBeanName());
if (debug) {
log.debug("Unregistered dependency for " + contextToString + " adding " + dependency
+ "; total unsatisfied [" + unsatisfiedDependencies + "]");
}
sendDependencyUnsatisfiedEvent(dependency);
sendBootstrappingDependenciesEvent(unsatisfiedDependencies.keySet());
} else {
if (debug) {
log.debug("Decreasing the number of matching services for " + contextToString + "; "
+ dependency + " still has " + count + " matches left");
}
}
break;
default: // do nothing
if (debug) {
log.debug("Unknown service event type for: " + dependency);
}
break;
}
} else {
if (trace) {
log.trace(dependency + " does not match: " + referenceToString);
}
}
}
}
}
/**
* Create a dependency manager, indicating the executor bound to, the context that contains the dependencies and the
* task to execute if all dependencies are met.
*
* @param executor
* @param context
* @param executeIfDone
*/
public DependencyServiceManager(ContextExecutorAccessor executor,
DelegatedExecutionOsgiBundleApplicationContext context,
List<OsgiServiceDependencyFactory> dependencyFactories, Runnable executeIfDone, long maxWaitTime) {
this.contextStateAccessor = executor;
this.context = context;
this.dependencyFactories = new ArrayList<OsgiServiceDependencyFactory>(8);
if (dependencyFactories != null)
this.dependencyFactories.addAll(dependencyFactories);
this.waitTime = maxWaitTime;
this.bundleContext = context.getBundleContext();
this.listener = new DependencyServiceListener();
this.executeIfDone = executeIfDone;
}
protected void findServiceDependencies() throws Exception {
try {
if (System.getSecurityManager() != null) {
final AccessControlContext acc = getAcc();
PrivilegedUtils.executeWithCustomTCCL(context.getClassLoader(),
new PrivilegedUtils.UnprivilegedThrowableExecution<Object>() {
public Object run() throws Throwable {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
doFindDependencies();
return null;
}
}, acc);
return null;
}
});
} else {
doFindDependencies();
}
} catch (Throwable th) {
if (th instanceof Exception)
throw ((Exception) th);
throw (Error) th;
}
if (log.isDebugEnabled()) {
log.debug(dependencies.size() + " OSGi service dependencies, " + unsatisfiedDependencies.size()
+ " unsatisfied (for beans " + unsatisfiedDependencies.values() + ") in "
+ context.getDisplayName());
}
if (!unsatisfiedDependencies.isEmpty()) {
log.info(context.getDisplayName() + " is waiting for unsatisfied dependencies ["
+ unsatisfiedDependencies.values() + "]");
}
if (log.isTraceEnabled()) {
log.trace("Total OSGi service dependencies beans " + dependencies.values());
log.trace("Unsatified OSGi service dependencies beans " + unsatisfiedDependencies.values());
}
}
private void doFindDependencies() throws Exception {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
boolean debug = log.isDebugEnabled();
boolean trace = log.isTraceEnabled();
if (trace)
log.trace("Looking for dependency factories inside bean factory [" + beanFactory.toString() + "]");
Map<String, OsgiServiceDependencyFactory> localFactories =
BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, OsgiServiceDependencyFactory.class, true,
false);
if (trace)
log.trace("Discovered local dependency factories: " + localFactories.keySet());
dependencyFactories.addAll(localFactories.values());
for (Iterator<OsgiServiceDependencyFactory> iterator = dependencyFactories.iterator(); iterator.hasNext();) {
OsgiServiceDependencyFactory dependencyFactory = iterator.next();
Collection<OsgiServiceDependency> discoveredDependencies = null;
if (trace) {
log.trace("Interogating dependency factory " + dependencyFactory);
}
try {
discoveredDependencies = dependencyFactory.getServiceDependencies(bundleContext, beanFactory);
} catch (Exception ex) {
log.warn("Dependency factory " + dependencyFactory
+ " threw exception while detecting dependencies for beanFactory " + beanFactory + " in "
+ context.getDisplayName(), ex);
throw ex;
}
// add the dependencies one by one
if (discoveredDependencies != null)
for (OsgiServiceDependency dependency : discoveredDependencies) {
if (dependency.isMandatory()) {
MandatoryServiceDependency msd = new MandatoryServiceDependency(bundleContext, dependency);
dependencies.put(msd, dependency.getBeanName());
if (!msd.isServicePresent()) {
log.info("Adding OSGi service dependency for importer [" + msd.getBeanName()
+ "] matching OSGi filter [" + msd.filterAsString + "]");
unsatisfiedDependencies.put(msd, dependency.getBeanName());
} else {
if (debug)
log.debug("OSGi service dependency for importer [" + msd.getBeanName()
+ "] is already satisfied");
}
}
}
}
}
protected boolean isSatisfied() {
return unsatisfiedDependencies.isEmpty();
}
public Map<MandatoryServiceDependency, String> getUnsatisfiedDependencies() {
return unsatisfiedDependencies;
}
protected void register() {
final String filter = createDependencyFilter();
if (log.isDebugEnabled()) {
log.debug(context.getDisplayName() + " has registered service dependency dependencyDetector with filter: "
+ filter);
}
// send dependency event before registering the filter
sendInitialBootstrappingEvents(unsatisfiedDependencies.keySet());
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAcc();
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
OsgiListenerUtils.addServiceListener(bundleContext, listener, filter);
return null;
}
}, acc);
} else {
OsgiListenerUtils.addServiceListener(bundleContext, listener, filter);
}
}
/**
* Look at all dependencies and create an appropriate filter. This method concatenates the filters into one. Note
* that not just unsatisfied dependencies are considered since their number can grow.
*
* @return
*/
private String createDependencyFilter() {
return createDependencyFilter(dependencies.keySet());
}
String createUnsatisfiedDependencyFilter() {
return createDependencyFilter(unsatisfiedDependencies.keySet());
}
private String createDependencyFilter(Collection<MandatoryServiceDependency> dependencies) {
if (dependencies.isEmpty()) {
return null;
}
boolean multiple = dependencies.size() > 1;
StringBuilder sb = new StringBuilder(dependencies.size() << 7);
if (multiple) {
sb.append("(|");
}
for (MandatoryServiceDependency dependency : dependencies) {
sb.append(dependency.filterAsString);
}
if (multiple) {
sb.append(')');
}
String filter = sb.toString();
return filter;
}
protected void deregister() {
if (log.isDebugEnabled()) {
log.debug("Deregistering service dependency dependencyDetector for " + context.getDisplayName());
}
OsgiListenerUtils.removeServiceListener(bundleContext, listener);
}
List<OsgiServiceDependencyEvent> getUnsatisfiedDependenciesAsEvents() {
return getUnsatisfiedDependenciesAsEvents(unsatisfiedDependencies.keySet());
}
private List<OsgiServiceDependencyEvent> getUnsatisfiedDependenciesAsEvents(
Collection<MandatoryServiceDependency> deps) {
List<OsgiServiceDependencyEvent> dependencies = new ArrayList<OsgiServiceDependencyEvent>(deps.size());
for (MandatoryServiceDependency entry : deps) {
OsgiServiceDependencyEvent nestedEvent =
new OsgiServiceDependencyWaitStartingEvent(context, entry.getServiceDependency(), waitTime);
dependencies.add(nestedEvent);
}
return Collections.unmodifiableList(dependencies);
}
// event notification
private void sendDependencyUnsatisfiedEvent(MandatoryServiceDependency dependency) {
OsgiServiceDependencyEvent nestedEvent =
new OsgiServiceDependencyWaitStartingEvent(context, dependency.getServiceDependency(), waitTime);
BootstrappingDependencyEvent dependencyEvent =
new BootstrappingDependencyEvent(context, context.getBundle(), nestedEvent);
publishEvent(dependencyEvent);
}
private void sendDependencySatisfiedEvent(MandatoryServiceDependency dependency) {
OsgiServiceDependencyEvent nestedEvent =
new OsgiServiceDependencyWaitEndedEvent(context, dependency.getServiceDependency(), waitTime);
BootstrappingDependencyEvent dependencyEvent =
new BootstrappingDependencyEvent(context, context.getBundle(), nestedEvent);
publishEvent(dependencyEvent);
}
private void sendInitialBootstrappingEvents(Set<MandatoryServiceDependency> deps) {
// send the fine grained event
List<OsgiServiceDependencyEvent> events = getUnsatisfiedDependenciesAsEvents(deps);
for (OsgiServiceDependencyEvent nestedEvent : events) {
BootstrappingDependencyEvent dependencyEvent =
new BootstrappingDependencyEvent(context, context.getBundle(), nestedEvent);
publishEvent(dependencyEvent);
}
// followed by the composite one
String filterAsString = createDependencyFilter(deps);
Filter filter = (filterAsString != null ? OsgiFilterUtils.createFilter(filterAsString) : null);
BootstrappingDependenciesEvent event =
new BootstrappingDependenciesEvent(context, context.getBundle(), events, filter, waitTime);
publishEvent(event);
}
private void sendBootstrappingDependenciesEvent(Set<MandatoryServiceDependency> deps) {
List<OsgiServiceDependencyEvent> events = getUnsatisfiedDependenciesAsEvents(deps);
String filterAsString = createDependencyFilter(deps);
Filter filter = (filterAsString != null ? OsgiFilterUtils.createFilter(filterAsString) : null);
BootstrappingDependenciesEvent event =
new BootstrappingDependenciesEvent(context, context.getBundle(), events, filter, waitTime);
publishEvent(event);
}
private void publishEvent(OsgiBundleApplicationContextEvent dependencyEvent) {
this.contextStateAccessor.getEventMulticaster().multicastEvent(dependencyEvent);
}
private AccessControlContext getAcc() {
AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
if (beanFactory instanceof ConfigurableBeanFactory) {
return ((ConfigurableBeanFactory) beanFactory).getAccessControlContext();
}
return null;
}
}