bug 347617 Dynamically install/update/remove OSGi bundles discovered in the contexts folder

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@3349 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Hugues Malphettes 2011-05-30 09:58:18 +00:00
parent 8e744c0595
commit d031732aec
6 changed files with 382 additions and 24 deletions

View File

@ -1,3 +1,6 @@
jetty-7.4.3-SNAPSHOT
+ 347617 Dynamically install/update/remove OSGi bundles discovered in the contexts folder
jetty-7.4.2.v20110526
+ 334443 Improve the ability to specify extra class paths using the Jetty Maven Plugin
+ 336220 tmp directory is not set if you reload a webapp with jetty-maven-plugin

View File

@ -16,7 +16,10 @@
<Arg>
<New id="NestedConnector" class="org.eclipse.jetty.nested.NestedConnector">
<Set name="statsOn">false</Set>
<Set name="confidentialPort">8443</Set>
<Set name="forwarded">true</Set>
<Set name="forwardedHostHeader">x-forwarded_for</Set>
<Set name="forwardedCipherSuiteHeader">sslclientcipher</Set>
<Set name="forwardedSslSessionIdHeader">sslsessionid</Set>
<Call name="addLifeCycleListener">
<Arg>
<New class="org.eclipse.jetty.osgi.nested.NestedConnectorListener" id="NestedConnectorListener">

View File

@ -157,7 +157,7 @@
</executions>
<configuration>
<instructions>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot</Bundle-SymbolicName>
<Export-Package>org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}"</Export-Package>
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
<!-- disable the uses directive: jetty will accomodate pretty much any versions

View File

@ -70,6 +70,7 @@ public class JettyBootstrapActivator implements BundleActivator
private JettyContextHandlerServiceTracker _jettyContextHandlerTracker;
private PackageAdminServiceTracker _packageAdminServiceTracker;
private BundleTracker _webBundleTracker;
private BundleContext _bundleContext;
// private ServiceRegistration _jettyServerFactoryService;
private JettyServerServiceTracker _jettyServerServiceTracker;
@ -86,6 +87,7 @@ public class JettyBootstrapActivator implements BundleActivator
public void start(BundleContext context) throws Exception
{
INSTANCE = this;
_bundleContext = context;
// track other bundles and fragments attached to this bundle that we
// should activate.
@ -298,18 +300,28 @@ public class JettyBootstrapActivator implements BundleActivator
*/
private static void checkBundleActivated()
{
if (INSTANCE == null) {
Bundle thisBundle = FrameworkUtil.getBundle(JettyBootstrapActivator.class);
try
{
thisBundle.start();
}
catch (BundleException e)
{
//nevermind.
}
}
if (INSTANCE == null)
{
Bundle thisBundle = FrameworkUtil.getBundle(JettyBootstrapActivator.class);
try
{
thisBundle.start();
}
catch (BundleException e)
{
// nevermind.
}
}
}
/**
* @return The bundle context for this bundle.
*/
public static BundleContext getBundleContext()
{
checkBundleActivated();
return INSTANCE._bundleContext;
}
}
}

View File

@ -17,17 +17,26 @@ package org.eclipse.jetty.osgi.boot;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.providers.ContextProvider;
import org.eclipse.jetty.deploy.providers.ScanningAppProvider;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
/**
* AppProvider for OSGi. Supports the configuration of ContextHandlers and
@ -42,6 +51,12 @@ import org.osgi.framework.Bundle;
* it supports the deployment of WebAppContexts. Except for the scanning of the
* webapps directory.
* </p>
* <p>
* When the parameter autoInstallOSGiBundles is set to true, OSGi bundles that
* are located in the monitored directory are installed and started after the
* framework as finished auto-starting all the other bundles.
* Warning: only use this for development.
* </p>
*/
public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
{
@ -50,7 +65,14 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
private boolean _parentLoaderPriority = false;
private String _defaultsDescriptor;
private String _tldBundles;
private String[] _configurationClasses;
private boolean _autoInstallOSGiBundles = true;
//Keep track of the bundles that were installed and that are waiting for the
//framework to complete its initialization.
Set<Bundle> _pendingBundlesToStart = null;
/**
* When a context file corresponds to a deployed bundle and is changed we
* reload the corresponding bundle.
@ -58,10 +80,15 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
private static class Filter implements FilenameFilter
{
OSGiAppProvider _enclosedInstance;
public boolean accept(File dir, String name)
{
if (!new File(dir,name).isDirectory())
File file = new File(dir,name);
if (fileMightBeAnOSGiBundle(file))
{
return true;
}
if (!file.isDirectory())
{
String contextName = getDeployedAppName(name);
if (contextName != null)
@ -144,15 +171,20 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
public ContextHandler createContextHandler(App app) throws Exception
{
// return pre-created Context
if (app.getContextHandler() != null)
ContextHandler wah = app.getContextHandler();
if (wah == null)
{
return app.getContextHandler();
// for some reason it was not defined when the App was constructed.
// we don't support this situation at this point.
// once the WebAppRegistrationHelper is refactored, the code
// that creates the ContextHandler will actually be here.
throw new IllegalStateException("The App must be passed the " + "instance of the ContextHandler when it is construsted");
}
// for some reason it was not defined when the App was constructed.
// we don't support this situation at this point.
// once the WebAppRegistrationHelper is refactored, the code
// that creates the ContextHandler will actually be here.
throw new IllegalStateException("The App must be passed the " + "instance of the ContextHandler when it is construsted");
if (_configurationClasses != null && wah instanceof WebAppContext)
{
((WebAppContext)wah).setConfigurationClasses(_configurationClasses);
}
return app.getContextHandler();
}
/**
@ -187,7 +219,7 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
// TODO apply configuration specific to this provider
if (context instanceof WebAppContext)
{
((WebAppContext)context).setExtractWAR(isExtract());
((WebAppContext)context).setExtractWAR(isExtract());
}
// wrap context as an App
@ -346,6 +378,24 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
_extractWars=extract;
}
/**
* @return true when this app provider locates osgi bundles and features in
* its monitored directory and installs them. false by default.
*/
public boolean isAutoInstallOSGiBundles()
{
return _autoInstallOSGiBundles;
}
/**
* &lt;autoInstallOSGiBundles&gt;true&lt;/autoInstallOSGiBundles&gt;
* @param installingOSGiBundles
*/
public void setAutoInstallOSGiBundles(boolean installingOSGiBundles)
{
_autoInstallOSGiBundles=installingOSGiBundles;
}
/* ------------------------------------------------------------ */
/**
@ -355,6 +405,10 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
* directory, then the ContextXmlDir is examined to see if a foo.xml file
* exists. If it does, then this deployer will not deploy the webapp and the
* ContextProvider should be used to act on the foo.xml file.
* </p>
* <p>
* Also if this directory contains some osgi bundles, it will install them.
* </p>
*
* @see ContextProvider
* @param contextsDir
@ -381,5 +435,264 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider
{
return _tldBundles;
}
/**
* @param configurations The configuration class names.
*/
public void setConfigurationClasses(String[] configurations)
{
_configurationClasses = configurations==null?null:(String[])configurations.clone();
}
/* ------------------------------------------------------------ */
/**
*
*/
public String[] getConfigurationClasses()
{
return _configurationClasses;
}
/**
* Overridden to install the OSGi bundles found in the monitored folder.
*/
protected void doStart() throws Exception
{
if (isAutoInstallOSGiBundles())
{
File scandir = getMonitoredDirResource().getFile();
for (File file : scandir.listFiles())
{
if (fileMightBeAnOSGiBundle(file))
{
installBundle(file, false);
}
}
}
super.doStart();
if (isAutoInstallOSGiBundles())
{
Scanner.ScanCycleListener scanCycleListner = new AutoStartWhenFrameworkHasCompleted(this);
super.addScannerListener(scanCycleListner);
}
}
/**
* When the file is a jar or a folder, we look if it looks like an OSGi bundle.
* In that case we install it and start it.
* <p>
* Really a simple trick to get going quickly with development.
* </p>
*/
@Override
protected void fileAdded(String filename) throws Exception
{
File file = new File(filename);
if (isAutoInstallOSGiBundles() && file.exists() && fileMightBeAnOSGiBundle(file))
{
installBundle(file, true);
}
else
{
super.fileAdded(filename);
}
}
/**
* @param file
* @return
*/
private static boolean fileMightBeAnOSGiBundle(File file)
{
if (file.isDirectory())
{
if (new File(file,"META-INF/MANIFEST.MF").exists())
{
return true;
}
}
else if (file.getName().endsWith(".jar"))
{
return true;
}
return false;
}
@Override
protected void fileChanged(String filename) throws Exception
{
File file = new File(filename);
if (isAutoInstallOSGiBundles() && fileMightBeAnOSGiBundle(file))
{
updateBundle(file);
}
else
{
super.fileChanged(filename);
}
}
@Override
protected void fileRemoved(String filename) throws Exception
{
File file = new File(filename);
if (isAutoInstallOSGiBundles() && fileMightBeAnOSGiBundle(file))
{
uninstallBundle(file);
}
else
{
super.fileRemoved(filename);
}
}
/**
* Returns a bundle according to its location.
* In the version 1.6 of org.osgi.framework, BundleContext.getBundle(String) is what we want.
* However to support older versions of OSGi. We use our own local refrence mechanism.
* @param location
* @return
*/
protected Bundle getBundle(BundleContext bc, String location)
{
//not available in older versions of OSGi:
//return bc.getBundle(location);
for (Bundle b : bc.getBundles())
{
if (b.getLocation().equals(location))
{
return b;
}
}
return null;
}
protected synchronized Bundle installBundle(File file, boolean start)
{
try
{
BundleContext bc = JettyBootstrapActivator.getBundleContext();
String location = file.toURI().toString();
Bundle b = getBundle(bc, location);
if (b == null)
{
b = bc.installBundle(location);
}
if (b == null)
{
//not sure we will ever be here,
//most likely a BundleException was thrown
Log.warn("The file " + location + " is not an OSGi bundle.");
return null;
}
if (start && b.getHeaders().get(Constants.FRAGMENT_HOST) == null)
{//not a fragment, try to start it. if the framework has finished auto-starting.
if (!PackageAdminServiceTracker.INSTANCE.frameworkHasCompletedAutostarts())
{
if (_pendingBundlesToStart == null)
{
_pendingBundlesToStart = new HashSet<Bundle>();
}
_pendingBundlesToStart.add(b);
return null;
}
else
{
b.start();
}
}
return b;
}
catch (BundleException e)
{
Log.warn("Unable to " + (start? "start":"install") + " the bundle " + file.getAbsolutePath(), e);
}
return null;
}
protected void uninstallBundle(File file)
{
try
{
Bundle b = getBundle(JettyBootstrapActivator.getBundleContext(), file.toURI().toString());
b.stop();
b.uninstall();
}
catch (BundleException e)
{
Log.warn("Unable to uninstall the bundle " + file.getAbsolutePath(), e);
}
}
protected void updateBundle(File file)
{
try
{
Bundle b = getBundle(JettyBootstrapActivator.getBundleContext(), file.toURI().toString());
if (b == null)
{
installBundle(file, true);
}
else if (b.getState() == Bundle.ACTIVE)
{
b.update();
}
else
{
b.start();
}
}
catch (BundleException e)
{
Log.warn("Unable to update the bundle " + file.getAbsolutePath(), e);
}
}
}
/**
* At the end of each scan, if there are some bundles to be started,
* look if the framework has completed its autostart. In that case start those bundles.
*/
class AutoStartWhenFrameworkHasCompleted implements Scanner.ScanCycleListener
{
private final OSGiAppProvider _appProvider;
AutoStartWhenFrameworkHasCompleted(OSGiAppProvider appProvider)
{
_appProvider = appProvider;
}
public void scanStarted(int cycle) throws Exception
{
}
public void scanEnded(int cycle) throws Exception
{
if (_appProvider._pendingBundlesToStart != null && PackageAdminServiceTracker.INSTANCE.frameworkHasCompletedAutostarts())
{
Iterator<Bundle> it = _appProvider._pendingBundlesToStart.iterator();
while (it.hasNext())
{
Bundle b = it.next();
if (b.getHeaders().get(Constants.FRAGMENT_HOST) != null)
{
continue;
}
try
{
b.start();
}
catch (BundleException e)
{
Log.warn("Unable to start the bundle " + b.getLocation(), e);
}
}
_appProvider._pendingBundlesToStart = null;
}
}
}

View File

@ -26,6 +26,7 @@ import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
/**
* When the PackageAdmin service is activated we can look for the fragments
@ -38,6 +39,9 @@ public class PackageAdminServiceTracker implements ServiceListener
private List<BundleActivator> _activatedFragments = new ArrayList<BundleActivator>();
private boolean _fragmentsWereActivated = false;
//Use the deprecated StartLevel to stay compatible with older versions of OSGi.
private StartLevel _startLevel;
private int _maxStartLevel = 6;
public static PackageAdminServiceTracker INSTANCE = null;
public PackageAdminServiceTracker(BundleContext context)
@ -66,6 +70,21 @@ public class PackageAdminServiceTracker implements ServiceListener
_fragmentsWereActivated = sr != null;
if (sr != null)
invokeFragmentActivators(sr);
sr = _context.getServiceReference(StartLevel.class.getName());
if (sr != null)
{
_startLevel = (StartLevel)_context.getService(sr);
try
{
_maxStartLevel = Integer.parseInt(System.getProperty("osgi.startLevel","6"));
}
catch (Exception e)
{
//nevermind default on the usual.
_maxStartLevel = 6;
}
}
return _fragmentsWereActivated;
}
@ -273,5 +292,13 @@ public class PackageAdminServiceTracker implements ServiceListener
}
}
}
/**
* @return true if the framework has completed all the start levels.
*/
public boolean frameworkHasCompletedAutostarts()
{
return _startLevel == null ? true : _startLevel.getStartLevel() >= _maxStartLevel;
}
}