blob: cfd006bfc550f4efd608f578f080d8bbc8d601cf [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2012 The University of Manchester
*
* Modifications to the initial code base are copyright of their
* respective authors, or their employers as appropriate.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
******************************************************************************/
package uk.org.taverna.osgi;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Logger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
/**
* OSGi Framework launcher.
*
* Handles loading and starting of OSGi bundles and Spring DM managed services.
*
* An implementation of an OSGi Service Platform Release 4.1 (or higher) must be available on the
* classpath.
*
* @author David Withers
*/
public class OsgiLauncher {
/**
* Default boot delegation packages.
*/
public static final String DEFAULT_BOOT_DELEGATION_PACKAGES = "sun.*,com.sun.*,java.*";
/**
* Default system packages.
*/
public static final String DEFAULT_SYSTEM_PACKAGES = "com.sun.org.apache.xml.internal.utils";
/**
* Default time to wait for services to start up.
*/
private static final long serviceLoadTimeoutSeconds = 30;
private static final Logger logger = Logger.getLogger(OsgiLauncher.class.getName());
private Framework framework;
private BundleContext context;
private Map<String, String> frameworkConfiguration = new HashMap<String, String>();
private List<URI> bundlesToInstall = new ArrayList<URI>();
private List<Bundle> installedBundles = new ArrayList<Bundle>();
private Set<String> startedSpringContexts = new HashSet<String>();
private Bundle springOsgiExtender;
private OsgiLauncher(File storageDirectory) {
setStorageDirectory(storageDirectory);
setCleanStorageDirectory(true);
setBootDelegationPackages(DEFAULT_BOOT_DELEGATION_PACKAGES);
setSystemPackages(DEFAULT_SYSTEM_PACKAGES);
}
/**
* Constructs an <code>OsgiLauncher</code> that loads bundles from a directory.
*
* Any file in the specified directory with a .jar extension will be loaded when the framework
* is started.
*
* @param storageDirectory
* persistent storage area used by the framework
* @param storageDirectory
* the directory containing bundles to load
*/
public OsgiLauncher(File storageDirectory, File bundleDirectory) {
this(storageDirectory);
List<File> jars = Arrays.asList(bundleDirectory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
}));
for (File jar : jars) {
bundlesToInstall.add(jar.toURI());
}
}
/**
* Constructs an <code>OsgiLauncher</code> that loads the specified bundles.
*
* @param storageDirectory
* persistent storage area used by the framework
* @param bundleURIs
* bundles to load
*/
public OsgiLauncher(File storageDirectory, List<URI> bundleURIs) {
this(storageDirectory);
for (URI bundleURI : bundleURIs) {
bundlesToInstall.add(bundleURI);
}
}
/**
* Starts the OSGi framework, installs and starts the bundles.
*
* @throws BundleException
* if the framework could not be started
*/
public void start() throws BundleException {
logger.info("Loading the OSGi Framework Factory");
FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator()
.next();
logger.info("Creating the OSGi Framework");
framework = frameworkFactory.newFramework(frameworkConfiguration);
logger.info("Starting the OSGi Framework");
framework.start();
context = framework.getBundleContext();
context.addServiceListener(new ServiceListener() {
public void serviceChanged(ServiceEvent event) {
ServiceReference serviceReference = event.getServiceReference();
if (event.getType() == ServiceEvent.REGISTERED) {
Object property = serviceReference
.getProperty("org.springframework.context.service.name");
if (property != null) {
addStartedSpringContext(property.toString());
}
}
logger.fine((event.getType() == ServiceEvent.REGISTERED ? "Registering : "
: "Unregistering : ") + serviceReference);
}
});
installedBundles = installBundles(bundlesToInstall);
List<Bundle> bundlesToStart = new ArrayList<Bundle>();
for (Bundle bundle : installedBundles) {
if ("org.springframework.osgi.extender".equals(bundle.getSymbolicName())) {
springOsgiExtender = bundle;
} else {
bundlesToStart.add(bundle);
}
}
startBundles(bundlesToStart);
}
/**
* Starts SpringDM managed services.
*
* @param waitForServices
* if true waits for services to start before returning
* @throws BundleException
* if the framework has not been started or a service could not be started
*/
public void startServices(boolean waitForServices) throws BundleException {
if (framework == null || framework.getState() != Bundle.ACTIVE) {
throw new BundleException("Framework not started");
}
if (springOsgiExtender != null) {
logger.info("Starting Spring OSGi Extender");
springOsgiExtender.start();
if (waitForServices) {
logger.info("Waiting for spring contexts to be started");
for (Bundle bundle : installedBundles) {
if (bundle.getState() == Bundle.ACTIVE) {
if (hasSpringContext(bundle)) {
logger.fine("Waiting for " + bundle.getSymbolicName());
waitForSpringContext(context, bundle.getSymbolicName(), serviceLoadTimeoutSeconds);
}
}
}
}
}
}
/**
* Stops the OSGI framework.
*
* @throws BundleException
* if the framework has not been started
* @throws InterruptedException
* if the thread is interrupted while the framework is stopping
*/
public void stop() throws BundleException, InterruptedException {
if (framework == null || framework.getState() != Bundle.ACTIVE) {
throw new BundleException("Framework not started");
}
framework.stop();
framework.waitForStop(0);
context = null;
}
/**
* Installs the bundles specified by the URIs into the framework.
*
* @param bundlesURIs
* the URIs of the bundles to install
* @return the installed bundles
* @throws BundleException
* if a bundle could not be installed
*/
public List<Bundle> installBundles(List<URI> bundlesURIs) throws BundleException {
List<Bundle> installedBundles = new ArrayList<Bundle>();
logger.info("Installing bundles into the OSGi Framework");
for (URI bundleURI : bundlesURIs) {
installedBundles.add(installBundle(bundleURI));
}
return installedBundles;
}
/**
* Installs the bundle specified by the URI into the framework.
*
* @param bundleURI
* the URI of the bundle to install
* @return the installed bundle
* @throws BundleException
* if the bundle could not be installed
*/
public Bundle installBundle(URI bundleURI) throws BundleException {
logger.fine("Installing bundle " + bundleURI);
return context.installBundle(bundleURI.toASCIIString());
}
/**
* Starts the bundles.
*
* If a bundle is a fragment bundle that bundle is not started.
*
* @param bundles
* the bundles to start
* @throws BundleException
* if a bundle could not be started
*/
public void startBundles(List<Bundle> bundles) throws BundleException {
logger.info("Starting bundles in the OSGi Framework");
for (Bundle bundle : bundles) {
startBundle(bundle);
}
}
/**
* Starts the bundle.
*
* If the bundle is a fragment bundle the bundle is not started.
*
* @param bundle
* the bundle to start
* @throws BundleException
* if the bundle could not be started
*/
public void startBundle(Bundle bundle) throws BundleException {
if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) {
logger.fine("Starting bundle " + bundle);
bundle.start();
}
}
/**
* Returns the context. Returns <code>null</code> if the framework is not started.
*
* @return the context
*/
public BundleContext getContext() {
return context;
}
/**
* Sets the configuration to use when creating the OSGi framework.
*
* @param frameworkConfiguration the configuration to use when creating the OSGi framework
*/
public void setFrameworkConfiguration(Map<String, String> frameworkConfiguration) {
this.frameworkConfiguration = frameworkConfiguration;
}
/**
* Adds boot delegation packages.
*
* Multiple packages must be separated by a ','.
*
* @param additionalBootDelegationPackages
* boot delegation packages to add
*/
public void addBootDelegationPackages(String additionalBootDelegationPackages) {
String bootDelegationPackages = frameworkConfiguration
.get(Constants.FRAMEWORK_BOOTDELEGATION);
if (bootDelegationPackages == null || bootDelegationPackages.isEmpty()) {
bootDelegationPackages = additionalBootDelegationPackages;
} else {
bootDelegationPackages = bootDelegationPackages + ","
+ additionalBootDelegationPackages;
}
frameworkConfiguration.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegationPackages);
}
/**
* Sets the boot delegation packages.
*
* Multiple packages must be separated by a ','.
*
* @param bootDelegationPackages
* the boot delegation packages
*/
public void setBootDelegationPackages(String bootDelegationPackages) {
frameworkConfiguration.put(Constants.FRAMEWORK_BOOTDELEGATION, bootDelegationPackages);
}
/**
* Adds system packages.
*
* Multiple packages must be separated by a ','.
*
* @param additionalSystemPackages
* system packages to add
*/
public void addSystemPackages(String additionalSystemPackages) {
String systemPackages = frameworkConfiguration
.get(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
if (systemPackages == null || systemPackages.isEmpty()) {
systemPackages = additionalSystemPackages;
} else {
systemPackages = systemPackages + "," + additionalSystemPackages;
}
frameworkConfiguration.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, systemPackages);
}
/**
* Sets the system packages.
*
* Multiple packages must be separated by a ','.
*
* @param systemPackages
* the system packages
*/
public void setSystemPackages(String systemPackages) {
frameworkConfiguration.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, systemPackages);
}
/**
* Sets the persistent storage area used by the framework.
*
* @param storageDirectory the persistent storage area used by the framework
*/
public void setStorageDirectory(File storageDirectory) {
frameworkConfiguration.put(Constants.FRAMEWORK_STORAGE, storageDirectory.getAbsolutePath());
}
/**
* Set whether the storage directory should be cleaned on startup.
*
* @param cleanStorageDirectory
* whether the storage directory should be cleaned on startup
*/
public void setCleanStorageDirectory(boolean cleanStorageDirectory) {
if (cleanStorageDirectory) {
frameworkConfiguration.put(Constants.FRAMEWORK_STORAGE_CLEAN,
Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
} else {
frameworkConfiguration.remove(Constants.FRAMEWORK_STORAGE_CLEAN);
}
}
/**
* Returns true if a bundle contains spring context files.
*
* @param bundle
* the bundle to check
* @return true if a bundle contains spring context files
*/
private boolean hasSpringContext(Bundle bundle) {
String springFilesLocation = "META-INF/spring";
// check for custom spring files location
@SuppressWarnings("rawtypes")
Dictionary headers = bundle.getHeaders();
if (headers != null) {
Object header = headers.get("Spring-Context");
if (header != null) {
springFilesLocation = header.toString().trim();
}
}
@SuppressWarnings("rawtypes")
Enumeration springFiles = bundle.findEntries(springFilesLocation, "*.xml", false);
return springFiles != null && springFiles.hasMoreElements();
}
private synchronized void waitForSpringContext(BundleContext context, String springContext,
long timeoutSeconds) {
long timeLeftToWait = timeoutSeconds * 1000;
long startTime = System.currentTimeMillis();
while (!startedSpringContexts.contains(springContext) && timeLeftToWait > 0) {
try {
wait(timeLeftToWait);
} catch (InterruptedException e) {}
timeLeftToWait = timeLeftToWait - (System.currentTimeMillis() - startTime);
}
}
private synchronized void addStartedSpringContext(String springContext) {
startedSpringContexts.add(springContext);
notifyAll();
}
}