/****************************************************************************** * 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.activator; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.SynchronousBundleListener; import org.osgi.framework.Version; import org.springframework.beans.BeanUtils; import org.springframework.beans.CachedIntrospectionResults; import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticaster; import org.eclipse.gemini.blueprint.extender.internal.support.ExtenderConfiguration; import org.eclipse.gemini.blueprint.extender.internal.support.NamespaceManager; import org.eclipse.gemini.blueprint.extender.support.internal.ConfigUtils; import org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean; import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceCollectionProxyFactoryBean; import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean; import org.eclipse.gemini.blueprint.util.OsgiBundleUtils; import org.eclipse.gemini.blueprint.util.OsgiStringUtils; /** * Osgi Extender that bootstraps 'Spring powered bundles'. * * <p/> The class listens to bundle events and manages the creation and destruction of application contexts for bundles * that have one or both of: <ul> <li>A manifest header entry Spring-Context <li>XML files in META-INF/spring folder * </ul> * * <p/> The extender also discovers any Spring namespace/schema handlers in resolved bundles and makes them available * through a dedicated OSGi service. * * <p/> The extender behaviour can be customized by attaching fragments to the extender bundle. On startup, the extender * will look for <code>META-INF/spring/*.xml</code> files and merge them into an application context. From the resulting * context, the context will look for beans with predefined names to determine its configuration. The current version * recognises the following bean names: * * <table border="1"> <tr> <th>Bean Name</th> <th>Bean Type</th> <th>Description</th> </tr> <tr> * <td><code>taskExecutor</code></td> <td><code>org.springframework.core.task.TaskExecutor</code></td> <td>Task executor * used for creating the discovered application contexts.</td> </tr> <tr> <td><code>shutdownTaskExecutor</code></td> * <td><code>org.springframework.core.task.TaskExecutor</code></td> <td>Task executor used for shutting down various * application contexts.</td> </tr> <tr> <td><code>extenderProperties</code></td> * <td><code>java.util.Properties</code></td> <td>Various properties for configuring the extender behaviour (see * below)</td> </tr> </table> * * <p/> <code>extenderProperties</code> recognises the following properties: * * <table border="1"> <tr> <th>Name</th> <th>Type</th> <th>Description</th> </tr> <tr> * <td><code>shutdown.wait.time</code></td> <td>Number</td> <td>The amount of time the extender will wait for each * application context to shutdown gracefully. Expressed in milliseconds.</td> </tr> <tr> * <td><code>process.annotations</code></td> <td>Boolean</td> <td>Whether or not, the extender will process SpringOSGi * annotations.</td> </tr> </table> * * <p/> Note: The extender configuration context is created during the bundle activation (a synchronous OSGi lifecycle * callback) and should contain only simple bean definitions that will not delay context initialisation. </p> * * @author Bill Gallagher * @author Andy Piper * @author Hal Hildebrand * @author Adrian Colyer * @author Costin Leau */ public class ContextLoaderListener implements BundleActivator { /** * Common base class for {@link ContextLoaderListener} listeners. * * @author Costin Leau */ private abstract class BaseListener implements SynchronousBundleListener { static final int LAZY_ACTIVATION_EVENT_TYPE = 0x00000200; protected final Log log = LogFactory.getLog(getClass()); /** * common cache used for tracking down bundles started lazily so they don't get processed twice (once when * started lazy, once when started fully) */ protected Map<Bundle, Object> lazyBundleCache = new WeakHashMap<Bundle, Object>(); /** dummy value for the bundle cache */ private final Object VALUE = new Object(); // caches the bundle protected void push(Bundle bundle) { synchronized (lazyBundleCache) { lazyBundleCache.put(bundle, VALUE); } } // checks the presence of the bundle as well as removing it protected boolean pop(Bundle bundle) { synchronized (lazyBundleCache) { return (lazyBundleCache.remove(bundle) != null); } } /** * A bundle has been started, stopped, resolved, or unresolved. This method is a synchronous callback, do not do * any long-running work in this thread. * * @see org.osgi.framework.SynchronousBundleListener#bundleChanged */ public void bundleChanged(BundleEvent event) { boolean trace = log.isTraceEnabled(); // check if the listener is still alive if (isClosed) { if (trace) log.trace("Listener is closed; events are being ignored"); return; } if (trace) { log.trace("Processing bundle event [" + OsgiStringUtils.nullSafeToString(event) + "] for bundle [" + OsgiStringUtils.nullSafeSymbolicName(event.getBundle()) + "]"); } try { handleEvent(event); } catch (Exception ex) { /* log exceptions before swallowing */ log.warn("Got exception while handling event " + event, ex); } } protected abstract void handleEvent(BundleEvent event); } /** * Bundle listener used for detecting namespace handler/resolvers. Exists as a separate listener so that it can be * registered early to avoid race conditions with bundles in INSTALLING state but still to avoid premature context * creation before the Spring {@link ContextLoaderListener} is not fully initialized. * * @author Costin Leau */ private class NamespaceBundleLister extends BaseListener { private final boolean resolved; NamespaceBundleLister(boolean resolvedBundles) { this.resolved = resolvedBundles; } protected void handleEvent(BundleEvent event) { Bundle bundle = event.getBundle(); switch (event.getType()) { case BundleEvent.RESOLVED: if (resolved) { maybeAddNamespaceHandlerFor(bundle, false); } break; case LAZY_ACTIVATION_EVENT_TYPE: { if (!resolved) { push(bundle); maybeAddNamespaceHandlerFor(bundle, true); } break; } case BundleEvent.STARTED: { if (!resolved) { if (!pop(bundle)) { maybeAddNamespaceHandlerFor(bundle, false); } } break; } case BundleEvent.STOPPED: { pop(bundle); maybeRemoveNameSpaceHandlerFor(bundle); break; } default: break; } } } /** * Bundle listener used for context creation/destruction. */ private class ContextBundleListener extends BaseListener { protected void handleEvent(BundleEvent event) { Bundle bundle = event.getBundle(); // ignore current bundle for context creation if (bundle.getBundleId() == bundleId) { return; } switch (event.getType()) { case LAZY_ACTIVATION_EVENT_TYPE: { // activate bundle try { bundle.loadClass("org.osgi.service.blueprint.container.BlueprintContainer"); } catch (Exception ex) { } break; } case BundleEvent.STARTED: { lifecycleManager.maybeCreateApplicationContextFor(bundle); break; } case BundleEvent.STOPPING: { if (OsgiBundleUtils.isSystemBundle(bundle)) { if (log.isDebugEnabled()) { log.debug("System bundle stopping"); } // System bundle is shutting down; Special handling for // framework shutdown shutdown(); } else { lifecycleManager.maybeCloseApplicationContextFor(bundle); } break; } default: break; } } } protected final Log log = LogFactory.getLog(getClass()); /** extender bundle id */ private long bundleId; /** extender configuration */ private ExtenderConfiguration extenderConfiguration; /** Spring namespace/resolver manager */ private NamespaceManager nsManager; /** The bundle's context */ private BundleContext bundleContext; /** Bundle listener interested in context creation */ private SynchronousBundleListener contextListener; /** Bundle listener interested in namespace resolvers/parsers discovery */ private SynchronousBundleListener nsListener; /** * Monitor used for dealing with the bundle activator and synchronous bundle threads */ private final Object monitor = new Object(); /** * flag indicating whether the context is down or not - useful during shutdown */ private volatile boolean isClosed = false; /** This extender version */ private Version extenderVersion; private volatile OsgiBundleApplicationContextEventMulticaster multicaster; private volatile LifecycleManager lifecycleManager; private volatile VersionMatcher versionMatcher; private volatile OsgiContextProcessor processor; private volatile ListListenerAdapter osgiListeners; /** * <p/> Called by OSGi when this bundle is started. Finds all previously resolved bundles and adds namespace * handlers for them if necessary. </p> <p/> Creates application contexts for bundles started before the extender * was started. </p> <p/> Registers a namespace/entity resolving service for use by web app contexts. </p> * * @see org.osgi.framework.BundleActivator#start */ public void start(BundleContext context) throws Exception { this.bundleContext = context; this.bundleId = context.getBundle().getBundleId(); this.extenderVersion = OsgiBundleUtils.getBundleVersion(context.getBundle()); log.info("Starting [" + bundleContext.getBundle().getSymbolicName() + "] bundle v.[" + extenderVersion + "]"); versionMatcher = new DefaultVersionMatcher(getManagedBundleExtenderVersionHeader(), extenderVersion); processor = createContextProcessor(); // init cache (to prevent ad-hoc Java Bean discovery on lazy bundles) initJavaBeansCache(); // Step 1 : discover existing namespaces (in case there are fragments with custom XML definitions) nsManager = new NamespaceManager(context); initNamespaceHandlers(bundleContext); // Step 2: initialize the extender configuration extenderConfiguration = initExtenderConfiguration(bundleContext); // init the OSGi event dispatch/listening system initListenerService(); // initialize the configuration once namespace handlers have been detected lifecycleManager = new LifecycleManager(extenderConfiguration, versionMatcher, createContextConfigFactory(), this.processor, getTypeCompatibilityChecker(), bundleContext); // Step 3: discover the bundles that are started // and require context creation initStartedBundles(bundleContext); } protected ExtenderConfiguration initExtenderConfiguration(BundleContext bundleContext) { return new ExtenderConfiguration(bundleContext, log); } protected OsgiContextProcessor createContextProcessor() { return new NoOpOsgiContextProcessor(); } protected TypeCompatibilityChecker getTypeCompatibilityChecker() { return null; } protected String getManagedBundleExtenderVersionHeader() { return ConfigUtils.EXTENDER_VERSION; } protected void initNamespaceHandlers(BundleContext context) { nsManager = new NamespaceManager(context); // register listener first to make sure any bundles in INSTALLED state // are not lost // if the property is defined and true, consider bundles in STARTED/LAZY-INIT state, otherwise use RESOLVED boolean nsResolved = !Boolean.getBoolean("org.eclipse.gemini.blueprint.ns.bundles.started"); nsListener = new NamespaceBundleLister(nsResolved); context.addBundleListener(nsListener); Bundle[] previousBundles = context.getBundles(); for (Bundle bundle : previousBundles) { // special handling for uber bundle being restarted if ((nsResolved && OsgiBundleUtils.isBundleResolved(bundle)) || (!nsResolved && OsgiBundleUtils.isBundleActive(bundle)) || bundleId == bundle.getBundleId()) { maybeAddNamespaceHandlerFor(bundle, false); } else if (OsgiBundleUtils.isBundleLazyActivated(bundle)) { maybeAddNamespaceHandlerFor(bundle, true); } } // discovery finished, publish the resolvers/parsers in the OSGi space nsManager.afterPropertiesSet(); } protected void initStartedBundles(BundleContext bundleContext) { // register the context creation listener contextListener = new ContextBundleListener(); // listen to any changes in bundles bundleContext.addBundleListener(contextListener); // get the bundles again to get an updated view Bundle[] previousBundles = bundleContext.getBundles(); // Instantiate all previously resolved bundles which are Spring // powered for (int i = 0; i < previousBundles.length; i++) { if (OsgiBundleUtils.isBundleActive(previousBundles[i])) { try { lifecycleManager.maybeCreateApplicationContextFor(previousBundles[i]); } catch (Throwable e) { log.warn("Cannot start bundle " + OsgiStringUtils.nullSafeSymbolicName(previousBundles[i]) + " due to", e); } } } } /** * Called by OSGi when this bundled is stopped. Unregister the namespace/entity resolving service and clear all * state. No further management of application contexts created by this extender prior to stopping the bundle occurs * after this point (even if the extender bundle is subsequently restarted). * * @see org.osgi.framework.BundleActivator#stop */ public void stop(BundleContext context) throws Exception { shutdown(); } /** * Shutdown the extender and all bundled managed by it. Shutdown of contexts is in the topological order of the * dependency graph formed by the service references. */ protected void shutdown() { synchronized (monitor) { // if already closed, bail out if (isClosed) return; else isClosed = true; } log.info("Stopping [" + bundleContext.getBundle().getSymbolicName() + "] bundle v.[" + extenderVersion + "]"); destroyJavaBeansCache(); // remove the bundle listeners (we are closing down) if (contextListener != null) { bundleContext.removeBundleListener(contextListener); contextListener = null; } if (nsListener != null) { bundleContext.removeBundleListener(nsListener); nsListener = null; } // close managed bundles lifecycleManager.destroy(); // clear the namespace registry nsManager.destroy(); // release multicaster if (multicaster != null) { multicaster.removeAllListeners(); multicaster = null; } // release listeners osgiListeners.destroy(); osgiListeners = null; extenderConfiguration.destroy(); } private void initJavaBeansCache() { Class<?>[] classes = new Class<?>[] { OsgiServiceFactoryBean.class, OsgiServiceProxyFactoryBean.class, OsgiServiceCollectionProxyFactoryBean.class }; CachedIntrospectionResults.acceptClassLoader(OsgiStringUtils.class.getClassLoader()); for (Class<?> clazz : classes) { BeanUtils.getPropertyDescriptors(clazz); } } private void destroyJavaBeansCache() { CachedIntrospectionResults.clearClassLoader(OsgiStringUtils.class.getClassLoader()); } protected void maybeAddNamespaceHandlerFor(Bundle bundle, boolean isLazy) { if (handlerBundleMatchesExtenderVersion(bundle)) nsManager.maybeAddNamespaceHandlerFor(bundle, isLazy); } protected void maybeRemoveNameSpaceHandlerFor(Bundle bundle) { if (handlerBundleMatchesExtenderVersion(bundle)) nsManager.maybeRemoveNameSpaceHandlerFor(bundle); } /** * Utility method that does extender range versioning and approapriate * * logging. * * @param bundle */ private boolean handlerBundleMatchesExtenderVersion(Bundle bundle) { if (!versionMatcher.matchVersion(bundle)) { if (log.isDebugEnabled()) log.debug("Ignoring handler bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "] due to mismatch in expected extender version"); return false; } return true; } protected ApplicationContextConfigurationFactory createContextConfigFactory() { return new DefaultApplicationContextConfigurationFactory(); } protected void initListenerService() { multicaster = extenderConfiguration.getEventMulticaster(); addApplicationListener(multicaster); multicaster.addApplicationListener(extenderConfiguration.getContextEventListener()); if (log.isDebugEnabled()) log.debug("Initialization of OSGi listeners service completed..."); } protected void addApplicationListener(OsgiBundleApplicationContextEventMulticaster multicaster) { osgiListeners = new ListListenerAdapter(bundleContext); osgiListeners.afterPropertiesSet(); // register the listener that does the dispatching multicaster.addApplicationListener(osgiListeners); } }