diff options
author | Hugues Malphettes | 2011-05-30 09:58:18 +0000 |
---|---|---|
committer | Hugues Malphettes | 2011-05-30 09:58:18 +0000 |
commit | d031732aece9e83671ee4f8f834a38fd70bc5af1 (patch) | |
tree | a5983ef3f8b65a22c0e77e082a8a5153c17abe88 | |
parent | 8e744c05955c0557420972df34b15c90a3f47d1e (diff) | |
download | org.eclipse.jetty.project-d031732aece9e83671ee4f8f834a38fd70bc5af1.tar.gz org.eclipse.jetty.project-d031732aece9e83671ee4f8f834a38fd70bc5af1.tar.xz org.eclipse.jetty.project-d031732aece9e83671ee4f8f834a38fd70bc5af1.zip |
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
6 files changed, 382 insertions, 24 deletions
diff --git a/VERSION.txt b/VERSION.txt index 42553deafa..198ca0feb6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -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 diff --git a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-osgi-nested-default.xml b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-osgi-nested-default.xml index a732f69a48..67d84e9df8 100644 --- a/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-osgi-nested-default.xml +++ b/jetty-osgi/jetty-osgi-boot/jettyhome/etc/jetty-osgi-nested-default.xml @@ -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"> diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index ed786dfa0f..9ac43c6d4f 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -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 diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java index 74c910c525..e3c0f9040d 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/JettyBootstrapActivator.java @@ -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; } -}
\ No newline at end of file +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java index efb27ee819..5bebe2e852 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiAppProvider.java @@ -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) + { + // 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) { - return app.getContextHandler(); + ((WebAppContext)wah).setConfigurationClasses(_configurationClasses); } - // 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"); + 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; + } + + /** + * <autoInstallOSGiBundles>true</autoInstallOSGiBundles> + * @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; + } + } } + diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java index ce8127a60a..f6229531fe 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/PackageAdminServiceTracker.java @@ -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; + } } |