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