/****************************************************************************** * 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.bundle; import java.io.IOException; import java.io.InputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.gemini.blueprint.context.BundleContextAware; import org.eclipse.gemini.blueprint.util.OsgiBundleUtils; import org.eclipse.gemini.blueprint.util.OsgiStringUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.ServiceReference; import org.osgi.service.startlevel.StartLevel; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * {@link Bundle} installer. * * <p/> This {@link FactoryBean} allows customers to use Spring to drive bundle management. Bundles states can be * modified using the <code>action</code> (defaults to <em>start</em>) and <code>destroyAction</code> (not set by * default) parameters. * * <p/> For example, to automatically install and start a bundle from the local maven repository (assuming the bundle * has been already retrieved), one can use the following configuration: * * <pre class="code"> <osgi:bundle id="aBundle" symbolic-name="org.company.bundles.a" * location="file:${localRepository }/org/company/bundles/a/${pom.version}/a-${pom.version}.jar" * action="start"/> </pre> * * * <p/> <strong>Note:</strong> Pay attention when installing bundles dynamically since classes can be loaded * aggressively. * * @author Andy Piper * @author Costin Leau * @see BundleAction */ public class BundleFactoryBean implements FactoryBean<Bundle>, BundleContextAware, InitializingBean, DisposableBean, ResourceLoaderAware { private static Log log = LogFactory.getLog(BundleFactoryBean.class); // bundle info /** Bundle location */ private String location; private Resource resource; private ResourceLoader resourceLoader = new DefaultResourceLoader(); /** Bundle symName */ private String symbolicName; /** Actual bundle */ private Bundle bundle; private BundleContext bundleContext; private BundleActionEnum action, destroyAction; private int startLevel; private ClassLoader classLoader; /** unused at the moment */ private boolean pushBundleAsContextClassLoader = false; // FactoryBean methods public Class<? extends Bundle> getObjectType() { return (bundle != null ? bundle.getClass() : Bundle.class); } public boolean isSingleton() { return true; } public Bundle getObject() throws Exception { return bundle; } public void afterPropertiesSet() throws Exception { Assert.notNull(bundleContext, "BundleContext is required"); // check parameters if (bundle == null && !StringUtils.hasText(symbolicName) && !StringUtils.hasText(location)) throw new IllegalArgumentException("at least one of symbolicName, location, bundle properties is required "); // try creating a resource if (getLocation() != null) { resource = resourceLoader.getResource(getLocation()); } // find the bundle first of all if (bundle == null) { bundle = findBundle(); } updateStartLevel(getStartLevel()); if (log.isDebugEnabled()) log.debug("working with bundle[" + OsgiStringUtils.nullSafeNameAndSymName(bundle)); if (log.isDebugEnabled()) log.debug("executing start-up action " + action); if (action != null) { executeAction(action); } } public void destroy() throws Exception { if (log.isDebugEnabled()) log.debug("executing shutdown action " + action); if (destroyAction != null) { executeAction(destroyAction); } bundle = null; classLoader = null; } protected void executeAction(BundleActionEnum action) { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); try { if (pushBundleAsContextClassLoader) { Thread.currentThread().setContextClassLoader(classLoader); } // switch statement // might look ugly but it's the only way to support the // install/update variants try { // Apply these actions only if we have a bundle, do not subsequently install a bundle // if none exists switch (action) { case INSTALL: bundle = installBundle(); break; case START: if (bundle == null) { bundle = installBundle(); } bundle.start(); break; case UPDATE: if (bundle == null) { bundle = installBundle(); } bundle.update(); break; case STOP: if (bundle != null) { bundle.stop(); } break; case UNINSTALL: if (bundle != null) { bundle.uninstall(); } break; default: // Default is to do nothing break; } } catch (BundleException be) { throw (RuntimeException) new IllegalStateException("cannot execute action " + action.name() + " on bundle " + OsgiStringUtils.nullSafeNameAndSymName(bundle)).initCause(be); } } finally { if (pushBundleAsContextClassLoader) { Thread.currentThread().setContextClassLoader(ccl); } } } /** * Install bundle - the equivalent of install action. * * @return * @throws BundleException */ private Bundle installBundle() throws BundleException { Assert.hasText(location, "location parameter required when installing a bundle"); // install bundle (default) log.info("Loading bundle from [" + location + "]"); Bundle bundle = null; boolean installBasedOnLocation = (resource == null); if (!installBasedOnLocation) { InputStream stream = null; try { stream = resource.getInputStream(); } catch (IOException ex) { // catch it since we fallback on normal install installBasedOnLocation = true; } if (!installBasedOnLocation) bundle = bundleContext.installBundle(location, stream); } if (installBasedOnLocation) bundle = bundleContext.installBundle(location); return bundle; } /** * Find a bundle based on the configuration (don't apply any actions for it). * * @return a Bundle instance based on the configuration. */ private Bundle findBundle() { // first consider symbolicName Bundle bundle = null; // try to find the bundle if (StringUtils.hasText(symbolicName)) bundle = OsgiBundleUtils.findBundleBySymbolicName(bundleContext, symbolicName); return bundle; } /** * Return the {@link Resource} object (if a {@link ResourceLoader} is available) from the given location (if any). * * @return {@link Resource} object for the given location */ public Resource getResource() { return resource; } /** * Return the given location. * * @return bundle location */ public String getLocation() { return location; } /** * Set the bundle location (optional operation). * * @param url bundle location (normally an URL or a Spring Resource) * */ public void setLocation(String url) { location = url; } /** * Return the given bundle symbolic name. * * @return bundle symbolic name */ public String getSymbolicName() { return symbolicName; } /** * Set the bundle symbolic name (optional operation). * * @param symbolicName bundle symbolic name */ public void setSymbolicName(String symbolicName) { this.symbolicName = symbolicName; } public void setBundleContext(BundleContext context) { bundleContext = context; } /** * Returns the action. * * @return Returns the action * @deprecated As of Spring DM 2.0, replaced by {@link #getBundleAction()} */ public BundleAction getAction() { return BundleAction.getBundleAction(action); } /** * Returns the bundle action. * * @return the bundle action. */ public BundleActionEnum getBundleAction() { return action; } /** * Action to execute at startup. * * @param action action to execute at startup * @deprecated As of Spring DM 2.0, replaced by {@link #setBundleAction(BundleAction)} */ public void setAction(BundleAction action) { this.action = action.getBundleActionEnum(); } /** * Action to execute at startup. * * @param action action to execute at startup */ public void setBundleAction(BundleActionEnum action) { this.action = action; } /** * Returns the destroyAction. * * @return Returns the destroyAction * @deprecated As of Spring DM 2.0, replaced by {@link #getBundleDestroyAction()} */ public BundleAction getDestroyAction() { return BundleAction.getBundleAction(destroyAction); } /** * Returns the bundle destroy action. * @return */ public BundleActionEnum getBundleDestroyAction() { return destroyAction; } /** * Action to execute at shutdown. * * @param action action to execute at shutdown * @deprecated As of Spring DM 2.0, replaced by {@link #setBundleDestroyAction(BundleActionEnum)} */ public void setDestroyAction(BundleAction action) { this.destroyAction = action.getBundleActionEnum(); } /** * Action to execute at shutdown. * * @param action action to execute at shutdown */ public void setBundleDestroyAction(BundleActionEnum action) { this.destroyAction = action; } /** * Gets the bundle start level. * * @return bundle start level */ public int getStartLevel() { return startLevel; } /** * Sets the bundle start level. * * @param startLevel bundle start level. */ public void setStartLevel(int startLevel) { this.startLevel = startLevel; } /** * Determines whether invocations on the remote service should be performed in the context (thread context class * loader) of the target bundle's ClassLoader. The default is <code>false</code>. * * @param pushBundleAsContextClassLoader true if the thread context class loader will be set to the target bundle or * false otherwise */ public void setPushBundleAsContextClassLoader(boolean pushBundleAsContextClassLoader) { this.pushBundleAsContextClassLoader = pushBundleAsContextClassLoader; } public void setClassLoader(ClassLoader classloader) { this.classLoader = classloader; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } // TODO: improve startlevel handling private void updateStartLevel(int level) { if (level == 0 || bundle == null) return; // Set the start level of the bundle if we are able. ServiceReference startref = bundleContext.getServiceReference(StartLevel.class.getName()); if (startref != null) { StartLevel start = (StartLevel) bundleContext.getService(startref); if (start != null) { start.setBundleStartLevel(bundle, level); } bundleContext.ungetService(startref); } } /** * Returns the bundle with which the class interacts. * * @return Returns this factory backing bundle */ public Bundle getBundle() { return bundle; } /** * Set the backing bundle used by this class. Allows programmatic configuration of already retrieved/created bundle. * * @param bundle The bundle to set */ public void setBundle(Bundle bundle) { this.bundle = bundle; } }