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