diff options
Diffstat (limited to 'jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot')
22 files changed, 2320 insertions, 1459 deletions
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 1c7250fcef..a7484296bf 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 @@ -18,8 +18,11 @@ import java.util.Dictionary; import java.util.Hashtable; import java.util.Properties; -import org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerExtender; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.JettyServerServiceTracker; +import org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper; import org.eclipse.jetty.osgi.boot.internal.webapp.JettyContextHandlerServiceTracker; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleTrackerCustomizer; import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; @@ -27,8 +30,8 @@ import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.BundleTracker; /** * Experiment: bootstrap jetty's complete distrib from an OSGi bundle. Progress: @@ -65,6 +68,11 @@ public class JettyBootstrapActivator implements BundleActivator private Server _server; private JettyContextHandlerServiceTracker _jettyContextHandlerTracker; private PackageAdminServiceTracker _packageAdminServiceTracker; + private BundleTracker _webBundleTracker; + +// private ServiceRegistration _jettyServerFactoryService; + private JettyServerServiceTracker _jettyServerServiceTracker; + /** * Setup a new jetty Server, registers it as a service. Setup the Service @@ -82,25 +90,30 @@ public class JettyBootstrapActivator implements BundleActivator // should activate. _packageAdminServiceTracker = new PackageAdminServiceTracker(context); - // todo: replace all this by the ManagedFactory so that we can start - // multiple jetty servers. - _server = new Server(); - // expose the server as a service. - _registeredServer = context.registerService(_server.getClass().getName(),_server,new Properties()); + _jettyServerServiceTracker = new JettyServerServiceTracker(); + context.addServiceListener(_jettyServerServiceTracker,"(objectclass=" + Server.class.getName() + ")"); + + //Register the Jetty Server Factory as a ManagedServiceFactory: +// Properties jettyServerMgdFactoryServiceProps = new Properties(); +// jettyServerMgdFactoryServiceProps.put("pid", OSGiWebappConstants.MANAGED_JETTY_SERVER_FACTORY_PID); +// _jettyServerFactoryService = context.registerService( +// ManagedServiceFactory.class.getName(), new JettyServersManagedFactory(), +// jettyServerMgdFactoryServiceProps); + + _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(_jettyServerServiceTracker); + // the tracker in charge of the actual deployment // and that will configure and start the jetty server. - _jettyContextHandlerTracker = new JettyContextHandlerServiceTracker(context,_server); - - // TODO: add a couple more checks on the properties? - // kind of nice not to so we can debug what is missing easily. context.addServiceListener(_jettyContextHandlerTracker,"(objectclass=" + ContextHandler.class.getName() + ")"); - // now ready to support the Extender pattern: - JettyContextHandlerExtender jettyContexHandlerExtender = new JettyContextHandlerExtender(); - context.addBundleListener(jettyContexHandlerExtender); - - jettyContexHandlerExtender.init(context); - + //see if we shoult start a default jetty instance right now. + DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(context); + + // now ready to support the Extender pattern: + _webBundleTracker = new BundleTracker(context, + Bundle.ACTIVE | Bundle.STOPPING, new WebBundleTrackerCustomizer()); + _webBundleTracker.open(); + } /* @@ -113,32 +126,68 @@ public class JettyBootstrapActivator implements BundleActivator { try { + + if (_webBundleTracker != null) + { + _webBundleTracker.close(); + _webBundleTracker = null; + } if (_jettyContextHandlerTracker != null) { _jettyContextHandlerTracker.stop(); context.removeServiceListener(_jettyContextHandlerTracker); + _jettyContextHandlerTracker = null; + } + if (_jettyServerServiceTracker != null) + { + _jettyServerServiceTracker.stop(); + context.removeServiceListener(_jettyServerServiceTracker); + _jettyServerServiceTracker = null; } if (_packageAdminServiceTracker != null) { _packageAdminServiceTracker.stop(); context.removeServiceListener(_packageAdminServiceTracker); + _packageAdminServiceTracker = null; } if (_registeredServer != null) { try { _registeredServer.unregister(); - _registeredServer = null; } catch (IllegalArgumentException ill) { // already unregistered. } + finally + { + _registeredServer = null; + } } +// if (_jettyServerFactoryService != null) +// { +// try +// { +// _jettyServerFactoryService.unregister(); +// } +// catch (IllegalArgumentException ill) +// { +// // already unregistered. +// } +// finally +// { +// _jettyServerFactoryService = null; +// } +// } + } finally { - _server.stop(); + if (_server != null) + { + _server.stop(); + } INSTANCE = null; } } @@ -148,8 +197,8 @@ public class JettyBootstrapActivator implements BundleActivator * registers it as an OSGi service. The tracker * {@link JettyContextHandlerServiceTracker} will do the actual deployment. * - * @param context - * The current bundle context + * @param contributor + * The bundle * @param webappFolderPath * The path to the root of the webapp. Must be a path relative to * bundle; either an absolute path. @@ -171,18 +220,15 @@ public class JettyBootstrapActivator implements BundleActivator * registers it as an OSGi service. The tracker * {@link JettyContextHandlerServiceTracker} will do the actual deployment. * - * @param context - * The current bundle context + * @param contributor + * The bundle * @param webappFolderPath * The path to the root of the webapp. Must be a path relative to * bundle; either an absolute path. * @param contextPath * The context path. Must start with "/" - * @param thisBundleInstallationOverride - * The location to a folder where the context file is located - * This overrides the default behavior that consists of using the - * location where the bundle is installed. Useful when in fact - * the webapp contributed is not inside a bundle. + * @param dic + * TODO: parameter description * @throws Exception */ public static void registerWebapplication(Bundle contributor, String webappFolderPath, String contextPath, Dictionary<String, String> dic) throws Exception @@ -220,17 +266,15 @@ public class JettyBootstrapActivator implements BundleActivator * @param contextFilePath * The path to the file inside the bundle that defines the * context. - * @param thisBundleInstallationOverride - * The location to a folder where the context file is located - * This overrides the default behavior that consists of using the - * location where the bundle is installed. Useful when in fact - * the webapp contributed is not inside a bundle. + * @param dic + * TODO: parameter description * @throws Exception */ public static void registerContext(Bundle contributor, String contextFilePath, Dictionary<String, String> dic) throws Exception { ContextHandler contextHandler = new ContextHandler(); dic.put(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH,contextFilePath); + dic.put(IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE,Boolean.TRUE.toString()); contributor.getBundleContext().registerService(ContextHandler.class.getName(),contextHandler,dic); } @@ -238,5 +282,6 @@ public class JettyBootstrapActivator implements BundleActivator { // todo } + }
\ 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 9ddc27bc74..eb93a8f3f7 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 @@ -1,5 +1,5 @@ // ======================================================================== -// Copyright (c) 2009 Mortbay, Inc. +// Copyright (c) 2009-2010 Mortbay, Inc. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 @@ -25,6 +25,8 @@ import org.eclipse.jetty.deploy.providers.ContextProvider; import org.eclipse.jetty.deploy.providers.ScanningAppProvider; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; /** * AppProvider for OSGi. Supports the configuration of ContextHandlers and @@ -43,7 +45,7 @@ import org.eclipse.jetty.util.resource.Resource; public class OSGiAppProvider extends ScanningAppProvider implements AppProvider { - private boolean _extractWars = false; + private boolean _extractWars = true; private boolean _parentLoaderPriority = false; private String _defaultsDescriptor; private String _tldBundles; @@ -91,8 +93,6 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider /** * Default OSGiAppProvider consutructed when none are defined in the * jetty.xml configuration. - * - * @param contextsDir */ public OSGiAppProvider() { @@ -111,7 +111,7 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider this(); setMonitoredDir(Resource.newResource(contextsDir.toURI())); } - + /** * Returns the ContextHandler that was created by WebappRegistractionHelper * @@ -140,19 +140,39 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider super.setDeploymentManager(deploymentManager); } + private static String getOriginId(Bundle contributor, String pathInBundle) + { + return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + + (pathInBundle.startsWith("/") ? pathInBundle : "/" + pathInBundle); + } + + /** + * @param context + * @throws Exception + */ + public void addContext(Bundle contributor, String pathInBundle, ContextHandler context) throws Exception + { + addContext(getOriginId(contributor, pathInBundle), context); + } /** * @param context * @throws Exception */ - public void addContext(ContextHandler context) throws Exception + public void addContext(String originId, ContextHandler context) throws Exception { // TODO apply configuration specific to this provider + if (context instanceof WebAppContext) + { + ((WebAppContext)context).setExtractWAR(isExtract()); + } // wrap context as an App - App app = new App(getDeploymentManager(),this,context.getDisplayName(),context); + App app = new App(getDeploymentManager(),this,originId,context); getDeployedApps().put(context.getDisplayName(),app); getDeploymentManager().addApp(app); } + + /** * Called by the scanner of the context files directory. If we find the @@ -274,6 +294,17 @@ public class OSGiAppProvider extends ScanningAppProvider implements AppProvider return null; } } + + public boolean isExtract() + { + return _extractWars; + } + + public void setExtract(boolean extract) + { + _extractWars=extract; + } + /* ------------------------------------------------------------ */ /** diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java new file mode 100644 index 0000000000..c2bddc290c --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiServerConstants.java @@ -0,0 +1,51 @@ +// ======================================================================== +// Copyright (c) 2009 Intalio, 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 +// 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: +// Hugues Malphettes - initial API and implementation +// ======================================================================== +package org.eclipse.jetty.osgi.boot; + +/** + * Name of the properties that configure a jetty Server OSGi service. + */ +public class OSGiServerConstants +{ + //for managed jetty instances, name of the configuration parameters + /** + * PID of the jetty servers's ManagedFactory + */ + public static final String MANAGED_JETTY_SERVER_FACTORY_PID = "org.eclipse.jetty.osgi.boot.managedserverfactory"; + + /** + * The associated value of that configuration parameter is the name under which this + * instance of the jetty server is tracked. + * When a ContextHandler is deployed and it specifies the managedServerName property, it is deployed + * on the corresponding jetty managed server or it throws an exception: jetty server not available. + */ + public static final String MANAGED_JETTY_SERVER_NAME = "managedServerName"; + /** + * Name of the 'default' jetty server instance. + * Usually the first one to be created. + */ + public static final String MANAGED_JETTY_SERVER_DEFAULT_NAME = "defaultJettyServer"; + + /** + * List of URLs to the jetty.xml files that configure th server. + */ + public static final String MANAGED_JETTY_XML_CONFIG_URLS = "jetty.etc.config.urls"; + + /** + * List of URLs to the folders where the legacy J2EE shared libraries are stored aka lib/ext, lib/jsp etc. + */ + public static final String MANAGED_JETTY_SHARED_LIB_FOLDER_URLS = "managedJettySharedLibFolderUrls"; + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java index c6c35d74f8..c6abc47c5b 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java @@ -15,7 +15,7 @@ package org.eclipse.jetty.osgi.boot; /** - * + * Name of the service properties for a ContextHandler that configure a webapp deployed on jetty OSGi. */ public class OSGiWebappConstants { @@ -66,13 +66,5 @@ public class OSGiWebappConstants * location if not null useful to install webapps or jetty context files * that are in fact not embedded in a bundle */ - public static final String SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE = "thisBundleInstall"; - - // sys prop config of jetty: - /** - * contains a comma separated list of pathes to the etc/jetty-*.xml files - * used to configure jetty. By default the value is 'etc/jetty.xml' when the - * path is relative the file is resolved relatively to jettyhome. - */ - public static final String SYS_PROP_JETTY_ETC_FILES = "jetty.etc.files"; + public static final String SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE = "thisBundleInstall"; } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java index 67104b7173..9c989c7f7e 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/jsp/TldLocatableURLClassloaderWithInsertedJettyClassloader.java @@ -30,7 +30,7 @@ public class TldLocatableURLClassloaderWithInsertedJettyClassloader extends TldL /** * - * @param osgiClassLoader + * @param osgiClassLoaderParent * The parent classloader * @param internalClassLoader * The classloader that will be at the same level than the diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java new file mode 100644 index 0000000000..7456cba037 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/DefaultJettyAtJettyHomeHelper.java @@ -0,0 +1,253 @@ +// ======================================================================== +// Copyright (c) 2010 Intalio, 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 +// 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: +// Hugues Malphettes - initial API and implementation +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.server.Server; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * Called by the {@link JettyBootstrapActivator} during the starting of the bundle. + * If the system property 'jetty.home' is defined and points to a folder, + * then setup the corresponding jetty server and starts it. + */ +public class DefaultJettyAtJettyHomeHelper { + + /** + * contains a comma separated list of pathes to the etc/jetty-*.xml files + * used to configure jetty. By default the value is 'etc/jetty.xml' when the + * path is relative the file is resolved relatively to jettyhome. + */ + public static final String SYS_PROP_JETTY_ETC_FILES = OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS; + + /** + * Usual system property used as the hostname for a typical jetty configuration. + */ + public static final String SYS_PROP_JETTY_HOME = "jetty.home"; + /** + * System property to point to a bundle that embeds a jetty configuration + * and that jetty configuration should be the default jetty server. + * First we look for jetty.home. If we don't find it then we look for this property. + */ + public static final String SYS_PROP_JETTY_HOME_BUNDLE = "jetty.home.bundle"; + /** + * Usual system property used as the hostname for a typical jetty configuration. + */ + public static final String SYS_PROP_JETTY_HOST = "jetty.host"; + /** + * Usual system property used as the port for http for a typical jetty configuration. + */ + public static final String SYS_PROP_JETTY_PORT = "jetty.port"; + /** + * Usual system property used as the port for https for a typical jetty configuration. + */ + public static final String SYS_PROP_JETTY_PORT_SSL = "jetty.port.ssl"; + + + /** + * Called by the JettyBootStrapActivator. + * If the system property jetty.home is defined and points to a folder, + * deploys the corresponding jetty server. + * <p> + * If the system property jetty.home.bundle is defined and points to a bundle. + * Look for the configuration of jetty inside that bundle and deploys the corresponding bundle. + * </p> + * <p> + * In both cases reads the system property 'jetty.etc.config.urls' to locate the configuration + * files for the deployed jetty. It is a comma spearate list of URLs or relative paths inside the bundle or folder + * to the config files. If underfined it defaults to 'etc/jetty.xml'. + * </p> + * <p> + * In both cases the system properties jetty.host, jetty.port and jetty.port.ssl are passed to the configuration files + * that might use them as part of their properties. + * </p> + */ + public static void startJettyAtJettyHome(BundleContext bundleContext) + { + String jettyHomeSysProp = System.getProperty(SYS_PROP_JETTY_HOME); + String jettyHomeBundleSysProp = System.getProperty(SYS_PROP_JETTY_HOME_BUNDLE); + File jettyHome = null; + Bundle jettyHomeBundle = null; + if (jettyHomeSysProp != null) + { + if (jettyHomeBundleSysProp != null) + { + System.err.println("WARN: both the jetty.home property and the jetty.home.bundle property are defined." + + " jetty.home.bundle is not taken into account."); + } + jettyHome = new File(jettyHomeSysProp); + if (!jettyHome.exists() || !jettyHome.isDirectory()) + { + System.err.println("Unable to locate the jetty.home folder " + jettyHomeSysProp); + return; + } + } + else if (jettyHomeBundleSysProp != null) + { + for (Bundle b : bundleContext.getBundles()) + { + if (b.getSymbolicName().equals(jettyHomeBundleSysProp)) + { + jettyHomeBundle = b; + break; + } + } + if (jettyHomeBundle == null) + { + System.err.println("Unable to find the jetty.home.bundle named " + jettyHomeSysProp); + return; + } + + } + if (jettyHome == null && jettyHomeBundle == null) + { + System.err.println("No default jetty started."); + return; + } + try + { + Server server = new Server(); + Properties properties = new Properties(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME); + + String configURLs = jettyHome != null ? getJettyConfigurationURLs(jettyHome) : getJettyConfigurationURLs(jettyHomeBundle); + properties.put(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS, configURLs); + + System.err.println("Configuring the default jetty server with " + configURLs); + + //these properties usually are the ones passed to this type of configuration. + setProperty(properties,SYS_PROP_JETTY_HOME,System.getProperty(SYS_PROP_JETTY_HOME)); + setProperty(properties,SYS_PROP_JETTY_HOST,System.getProperty(SYS_PROP_JETTY_HOST)); + setProperty(properties,SYS_PROP_JETTY_PORT,System.getProperty(SYS_PROP_JETTY_PORT)); + setProperty(properties,SYS_PROP_JETTY_PORT_SSL,System.getProperty(SYS_PROP_JETTY_PORT_SSL)); + + bundleContext.registerService(Server.class.getName(), server, properties); + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + + /** + * Minimum setup for the location of the configuration files given a jettyhome folder. + * Reads the system property jetty.etc.config.urls and look for the corresponding jetty + * configuration files that will be used to setup the jetty server. + * @param jettyhome + * @return + */ + private static String getJettyConfigurationURLs(File jettyhome) + { + String jettyetc = System.getProperty(SYS_PROP_JETTY_ETC_FILES,"etc/jetty.xml"); + StringTokenizer tokenizer = new StringTokenizer(jettyetc,";,", false); + StringBuilder res = new StringBuilder(); + while (tokenizer.hasMoreTokens()) + { + String next = tokenizer.nextToken().trim(); + if (!next.startsWith("/") && next.indexOf(':') == -1) + { + try { + next = new File(jettyhome, next).toURI().toURL().toString(); + } catch (MalformedURLException e) { + e.printStackTrace(); + continue; + } + } + appendToCommaSeparatedList(res, next); + } + return res.toString(); + } + + /** + * Minimum setup for the location of the configuration files given a configuration + * embedded inside a bundle. + * Reads the system property jetty.etc.config.urls and look for the corresponding jetty + * configuration files that will be used to setup the jetty server. + * @param jettyhome + * @return + */ + private static String getJettyConfigurationURLs(Bundle configurationBundle) + { + String jettyetc = System.getProperty(SYS_PROP_JETTY_ETC_FILES,"etc/jetty.xml"); + System.err.println("jettyetc=" + jettyetc); + StringTokenizer tokenizer = new StringTokenizer(jettyetc,";,", false); + StringBuilder res = new StringBuilder(); + + while (tokenizer.hasMoreTokens()) + { + String etcFile = tokenizer.nextToken().trim(); + if (etcFile.startsWith("/") || etcFile.indexOf(":") != -1) + { + appendToCommaSeparatedList(res, etcFile); + } + else + { + Enumeration<URL> enUrls = BundleFileLocatorHelper.DEFAULT + .findEntries(configurationBundle, etcFile); + + //default for org.eclipse.osgi.boot where we look inside jettyhome for the default embedded configuration. + //default inside jettyhome. this way fragments to the bundle can define their own configuration. + if ((enUrls == null || !enUrls.hasMoreElements()) && etcFile.endsWith("etc/jetty.xml")) + { + enUrls = BundleFileLocatorHelper.DEFAULT + .findEntries(configurationBundle, "/jettyhome/etc/jetty-osgi-default.xml"); + System.err.println("Configuring jetty with the default embedded configuration:" + + "bundle: " + configurationBundle.getSymbolicName() + + " config: /jettyhome/etc/jetty-osgi-default.xml"); + } + if (enUrls == null || !enUrls.hasMoreElements()) + { + System.err.println("Unable to locate a jetty configuration file for " + etcFile); + } + if (enUrls != null) + { + while (enUrls.hasMoreElements()) + { + appendToCommaSeparatedList(res, enUrls.nextElement().toString()); + } + } + } + } + return res.toString(); + } + + private static void appendToCommaSeparatedList(StringBuilder buffer, String value) + { + if (buffer.length() != 0) + { + buffer.append(","); + } + buffer.append(value); + } + + private static void setProperty(Properties properties, String key, String value) + { + if (value != null) + { + properties.put(key, value); + } + } + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java new file mode 100644 index 0000000000..796447496d --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/IManagedJettyServerRegistry.java @@ -0,0 +1,28 @@ +// ======================================================================== +// Copyright (c) 2010 Intalio, 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 +// 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: +// Hugues Malphettes - initial API and implementation +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +/** + * Keeps track of the running jetty servers. They are named. + */ +public interface IManagedJettyServerRegistry { + + /** + * @param managedServerName The server name + * @return the corresponding jetty server wrapped with its deployment properties. + */ + public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName); + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java new file mode 100644 index 0000000000..200647cecc --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServerServiceTracker.java @@ -0,0 +1,161 @@ +// ======================================================================== +// Copyright (c) 2009-2010 Intalio, 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 +// 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. +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.server.Server; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; + +/** + * Deploy the jetty server instances when they are registered as an OSGi service. + */ +public class JettyServerServiceTracker implements ServiceListener, IManagedJettyServerRegistry +{ + + /** + * Servers indexed by PIDs. PIDs are generated by the ConfigurationAdmin service. + */ + private Map<String, ServerInstanceWrapper> _serversIndexedByName = new HashMap<String, ServerInstanceWrapper>(); + /** The context-handler to deactivate indexed by ServerInstanceWrapper */ + private Map<ServiceReference, ServerInstanceWrapper> _indexByServiceReference = new HashMap<ServiceReference, ServerInstanceWrapper>(); + + + /** + * Stops each one of the registered servers. + */ + public void stop() + { + //not sure that this is really useful but here we go. + for (ServerInstanceWrapper wrapper : _serversIndexedByName.values()) + { + try + { + wrapper.stop(); + } + catch (Throwable t) + { + + } + } + } + + + /** + * Receives notification that a service has had a lifecycle change. + * + * @param ev + * The <code>ServiceEvent</code> object. + */ + public void serviceChanged(ServiceEvent ev) + { + ServiceReference sr = ev.getServiceReference(); + switch (ev.getType()) + { + case ServiceEvent.MODIFIED: + case ServiceEvent.UNREGISTERING: + { + ServerInstanceWrapper instance = unregisterInIndex(ev.getServiceReference()); + if (instance != null) + { + try + { + instance.stop(); + } + catch (Exception e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + if (ev.getType() == ServiceEvent.UNREGISTERING) + { + break; + } + else + { + // modified, meaning: we reload it. now that we stopped it; + // we can register it. + } + case ServiceEvent.REGISTERED: + { + Bundle contributor = sr.getBundle(); + Server server = (Server)contributor.getBundleContext().getService(sr); + ServerInstanceWrapper wrapper = registerInIndex(server, sr); + Properties props = new Properties(); + for (String key : sr.getPropertyKeys()) + { + Object value = sr.getProperty(key); + props.put(key, value); + } + wrapper.start(server, props); + break; + } + } + } + + private ServerInstanceWrapper registerInIndex(Server server, ServiceReference sr) + { + String name = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + if (name == null) + { + throw new IllegalArgumentException("The property " + + OSGiServerConstants.MANAGED_JETTY_SERVER_NAME + " is mandatory"); + } + ServerInstanceWrapper wrapper = new ServerInstanceWrapper(name); + _indexByServiceReference.put(sr,wrapper); + _serversIndexedByName.put(name,wrapper); + return wrapper; + } + + /** + * Returns the ContextHandler to stop. + * + * @param reg + * @return the ContextHandler to stop. + */ + private ServerInstanceWrapper unregisterInIndex(ServiceReference sr) + { + ServerInstanceWrapper handler = _indexByServiceReference.remove(sr); + if (handler == null) + { + // a warning? + return null; + } + String name = handler.getManagedServerName(); + if (name != null) + { + _serversIndexedByName.remove(name); + } + return handler; + } + + /** + * @param managedServerName The server name + * @return the corresponding jetty server wrapped with its deployment properties. + */ + public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) + { + return _serversIndexedByName.get(managedServerName == null + ? OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME : managedServerName); + } + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServersManagedFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServersManagedFactory.java index d6b8aa9853..31ad22534b 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServersManagedFactory.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServersManagedFactory.java @@ -14,45 +14,30 @@ // ======================================================================== package org.eclipse.jetty.osgi.boot.internal.serverfactory; +import java.net.URL; import java.util.Dictionary; import java.util.HashMap; +import java.util.Hashtable; import java.util.Map; +import java.util.StringTokenizer; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; import org.eclipse.jetty.server.Server; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedServiceFactory; /** - * This is a work in progress. <br/> - * In particular there is a lot of work required during the update of the - * configuration of a server. It might not be practical to in fact support that - * and re-deploy the webapps in the same state than before the server was - * stopped. - * <p> - * jetty servers are managed as OSGi services registered here. try to find out - * if a configuration will fail (ports already opened etc). - * </p> - * <p> - * Try to enable the creation and configuration of jetty servers in all the - * usual standard ways. The configuration of the server is defined by the - * properties passed to the service: - * <ol> - * <li>First look for jettyfactory. If the value is a jetty server, use that - * server</li> - * <li>Then look for jettyhome key. The value should be a java.io.File or a - * String that is a path to the folder It is required that a etc/jetty.xml file - * will be loated from that folder.</li> - * <li>Then look for a jettyxml key. The value should be a java.io.File or an - * InputStream that contains a jetty configuration file.</li> - * <li>TODO: More ways to configure a jetty server? (other IOCs like spring, - * equinox properties...)</li> - * <li>Throw an exception if none of the relevant parameters are found</li> - * </ol> - * </p> + * Manages the deployment of jetty server instances. + * Not sure this is bringing much compared to the JettyServerServiceTracker. * * @author hmalphettes */ -public class JettyServersManagedFactory implements ManagedServiceFactory +public class JettyServersManagedFactory implements ManagedServiceFactory, IManagedJettyServerRegistry { /** @@ -79,7 +64,18 @@ public class JettyServersManagedFactory implements ManagedServiceFactory */ public static final String JETTY_HTTPS_PORT = "jetty.http.port"; - private Map<String, Server> _servers = new HashMap<String, Server>(); + /** + * Servers indexed by PIDs. PIDs are generated by the ConfigurationAdmin service. + */ + private Map<String, ServerInstanceWrapper> _serversIndexedByPID = new HashMap<String, ServerInstanceWrapper>(); + /** + * PID -> {@link OSGiWebappConstants#MANAGED_JETTY_SERVER_NAME} + */ + private Map<String, String> _serversNameIndexedByPID = new HashMap<String, String>(); + /** + * {@link OSGiWebappConstants#MANAGED_JETTY_SERVER_NAME} -> PID + */ + private Map<String, String> _serversPIDIndexedByName = new HashMap<String, String>(); /** * Return a descriptive name of this factory. @@ -93,24 +89,43 @@ public class JettyServersManagedFactory implements ManagedServiceFactory public void updated(String pid, Dictionary properties) throws ConfigurationException { - Server server = _servers.get(pid); + ServerInstanceWrapper serverInstanceWrapper = getServerByPID(pid); deleted(pid); // do we need to collect the currently deployed http services and // webapps // to be able to re-deploy them later? // probably not. simply restart and see the various service trackers // do everything that is needed. - + String name = (String)properties.get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + if (name == null) + { + throw new ConfigurationException(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, + "The name of the server is mandatory"); + } + serverInstanceWrapper = new ServerInstanceWrapper(name); + _serversIndexedByPID.put(pid, serverInstanceWrapper); + _serversNameIndexedByPID.put(pid, name); + _serversPIDIndexedByName.put(name, pid); + serverInstanceWrapper.start(new Server(), properties); } public synchronized void deleted(String pid) { - Server server = (Server)_servers.remove(pid); + ServerInstanceWrapper server = (ServerInstanceWrapper)_serversIndexedByPID.remove(pid); + String name = _serversNameIndexedByPID.remove(pid); + if (name != null) + { + _serversPIDIndexedByName.remove(name); + } + else + { + //something incorrect going on. + } if (server != null) { try { - server.stop(); + server.stop(); } catch (Exception e) { @@ -119,4 +134,74 @@ public class JettyServersManagedFactory implements ManagedServiceFactory } } + public synchronized ServerInstanceWrapper getServerByPID(String pid) + { + return _serversIndexedByPID.get(pid); + } + + /** + * @param managedServerName The server name + * @return the corresponding jetty server wrapped with its deployment properties. + */ + public ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) + { + String pid = _serversPIDIndexedByName.get(managedServerName); + return pid != null ? _serversIndexedByPID.get(pid) : null; + } + + /** + * Helper method to create and configure a new Jetty Server via the ManagedServiceFactory + * @param contributor + * @param serverName + * @param urlsToJettyXml + * @throws Exception + */ + public static void createNewServer(Bundle contributor, String serverName, String urlsToJettyXml) throws Exception + { + ServiceReference configurationAdminReference = + contributor.getBundleContext().getServiceReference( ConfigurationAdmin.class.getName() ); + + ConfigurationAdmin confAdmin = (ConfigurationAdmin) contributor.getBundleContext() + .getService( configurationAdminReference ); + + Configuration configuration = confAdmin.createFactoryConfiguration( + OSGiServerConstants.MANAGED_JETTY_SERVER_FACTORY_PID, contributor.getLocation() ); + Dictionary properties = new Hashtable(); + properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, serverName); + + StringBuilder actualBundleUrls = new StringBuilder(); + StringTokenizer tokenizer = new StringTokenizer(urlsToJettyXml, ",", false); + while (tokenizer.hasMoreTokens()) + { + if (actualBundleUrls.length() != 0) + { + actualBundleUrls.append(","); + } + String token = tokenizer.nextToken(); + if (token.indexOf(':') != -1) + { + //a complete url. no change needed: + actualBundleUrls.append(token); + } + else if (token.startsWith("/")) + { + //url relative to the contributor bundle: + URL url = contributor.getEntry(token); + if (url == null) + { + actualBundleUrls.append(token); + } + else + { + actualBundleUrls.append(url.toString()); + } + } + + } + + properties.put(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS, actualBundleUrls.toString()); + configuration.update(properties); + + } + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java new file mode 100644 index 0000000000..7e4afc5b29 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -0,0 +1,422 @@ +// ======================================================================== +// Copyright (c) 2009 Intalio, 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 +// 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. +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.serverfactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiAppProvider; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; +import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader; +import org.eclipse.jetty.osgi.boot.internal.webapp.LibExtClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.internal.webapp.WebBundleDeployerHelper; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.xml.sax.SAXParseException; + + +/** + * Exposes a Jetty Server to be managed by an OSGi ManagedServiceFactory + * Configure and start it. + * Can also be used from the ManagedServiceFactory + */ +public class ServerInstanceWrapper { + + private static Logger __logger = Log.getLogger(ServerInstanceWrapper.class.getName()); + + private final String _managedServerName; + + /** + * The managed jetty server + */ + private Server _server; + private ContextHandlerCollection _ctxtHandler; + + /** + * This is the class loader that should be the parent classloader of any + * webapp classloader. It is in fact the _libExtClassLoader with a trick to + * let the TldScanner find the jars where the tld files are. + */ + private ClassLoader _commonParentClassLoaderForWebapps; + private DeploymentManager _deploymentManager; + private OSGiAppProvider _provider; + + private WebBundleDeployerHelper _webBundleDeployerHelper; + + + public ServerInstanceWrapper(String managedServerName) + { + _managedServerName = managedServerName; + } + + public String getManagedServerName() + { + return _managedServerName; + } + + /** + * The classloader that should be the parent classloader for + * each webapp deployed on this server. + * @return + */ + public ClassLoader getParentClassLoaderForWebapps() + { + return _commonParentClassLoaderForWebapps; + } + + /** + * @return The deployment manager registered on this server. + */ + public DeploymentManager getDeploymentManager() + { + return _deploymentManager; + } + + /** + * @return The app provider registered on this server. + */ + public OSGiAppProvider getOSGiAppProvider() + { + return _provider; + } + + + public Server getServer() + { + return _server; + } + + + public WebBundleDeployerHelper getWebBundleDeployerHelp() + { + return _webBundleDeployerHelper; + } + + /** + * @return The collection of context handlers + */ + public ContextHandlerCollection getContextHandlerCollection() + { + return _ctxtHandler; + } + + + public void start(Server server, Dictionary props) + { + _server = server; + ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); + try + { + // passing this bundle's classloader as the context classlaoder + // makes sure there is access to all the jetty's bundles + ClassLoader libExtClassLoader = null; + String sharedURLs = (String)props.get(OSGiServerConstants.MANAGED_JETTY_SHARED_LIB_FOLDER_URLS); + try + { + List<File> shared = sharedURLs != null ? extractFiles(sharedURLs) : null; + libExtClassLoader = LibExtClassLoaderHelper.createLibExtClassLoader( + shared, null, server, JettyBootstrapActivator.class.getClassLoader()); + } + catch (MalformedURLException e) + { + e.printStackTrace(); + } + + Thread.currentThread().setContextClassLoader(libExtClassLoader); + + configure(server, props); + + init(); + + //now that we have an app provider we can call the registration customizer. + try + { + URL[] jarsWithTlds = getJarsWithTlds(); + _commonParentClassLoaderForWebapps = jarsWithTlds == null + ? libExtClassLoader + :new TldLocatableURLClassloader(libExtClassLoader,jarsWithTlds); + } + catch (MalformedURLException e) + { + e.printStackTrace(); + } + + + server.start(); + } + catch (Throwable t) + { + t.printStackTrace(); + } + finally + { + Thread.currentThread().setContextClassLoader(contextCl); + } + _webBundleDeployerHelper = new WebBundleDeployerHelper(this); + } + + + public void stop() + { + try { + if (_server.isRunning()) + { + _server.stop(); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. + * Should support a way to plug more bundles that contain taglibs. + * + * The jasper TldScanner expects a URLClassloader to parse a jar for the + * /META-INF/*.tld it may contain. We place the bundles that we know contain + * such tag-libraries. Please note that it will work if and only if the + * bundle is a jar (!) Currently we just hardcode the bundle that contains + * the jstl implemenation. + * + * A workaround when the tld cannot be parsed with this method is to copy + * and paste it inside the WEB-INF of the webapplication where it is used. + * + * Support only 2 types of packaging for the bundle: - the bundle is a jar + * (recommended for runtime.) - the bundle is a folder and contain jars in + * the root and/or in the lib folder (nice for PDE developement situations) + * Unsupported: the bundle is a jar that embeds more jars. + * + * @return + * @throws Exception + */ + private URL[] getJarsWithTlds() throws Exception + { + ArrayList<URL> res = new ArrayList<URL>(); + WebBundleDeployerHelper.staticInit();//that is not looking great. + for (WebappRegistrationCustomizer regCustomizer : WebBundleDeployerHelper.JSP_REGISTRATION_HELPERS) + { + URL[] urls = regCustomizer.getJarsWithTlds(_provider, WebBundleDeployerHelper.BUNDLE_FILE_LOCATOR_HELPER); + for (URL url : urls) + { + if (!res.contains(url)) + res.add(url); + } + } + if (!res.isEmpty()) + return res.toArray(new URL[res.size()]); + else + return null; + } + + private void configure(Server server, Dictionary props) throws Exception + { + String jettyConfigurationUrls = (String) props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS); + List<URL> jettyConfigurations = jettyConfigurationUrls != null + ? extractResources(jettyConfigurationUrls) : null; + if (jettyConfigurations == null || jettyConfigurations.isEmpty()) + { + return; + } + Map<Object,Object> id_map = new HashMap<Object,Object>(); + id_map.put("Server",server); + Map<Object,Object> properties = new HashMap<Object,Object>(); + Enumeration en = props.keys(); + while (en.hasMoreElements()) + { + Object key = en.nextElement(); + Object value = props.get(key); + properties.put(key, value); + } + + for (URL jettyConfiguration : jettyConfigurations) + { + InputStream is = null; + try + { + // Execute a Jetty configuration file + is = jettyConfiguration.openStream(); + XmlConfiguration config = new XmlConfiguration(is); + config.setIdMap(id_map); + config.setProperties(properties); + config.configure(); + id_map=config.getIdMap(); + } + catch (SAXParseException saxparse) + { + __logger.warn("Unable to configure the jetty/etc file " + jettyConfiguration,saxparse); + throw saxparse; + } + finally + { + IO.close(is); + } + } + + } + + + /** + * Must be called after the server is configured. + * + * Locate the actual instance of the ContextDeployer and WebAppDeployer that + * was created when configuring the server through jetty.xml. If there is no + * such thing it won't be possible to deploy webapps from a context and we + * throw IllegalStateExceptions. + */ + private void init() + { + // Get the context handler + _ctxtHandler = (ContextHandlerCollection)_server.getChildHandlerByClass(ContextHandlerCollection.class); + + // get a deployerManager + List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class); + if (deployers != null && !deployers.isEmpty()) + { + _deploymentManager = deployers.get(0); + + for (AppProvider provider : _deploymentManager.getAppProviders()) + { + if (provider instanceof OSGiAppProvider) + { + _provider=(OSGiAppProvider)provider; + break; + } + } + if (_provider == null) + { + //create it on the fly with reasonable default values. + try + { + _provider = new OSGiAppProvider(); + _provider.setMonitoredDir( + Resource.newResource(getDefaultOSGiContextsHome( + new File(System.getProperty("jetty.home"))).toURI())); + } catch (IOException e) { + e.printStackTrace(); + } + _deploymentManager.addAppProvider(_provider); + } + } + + if (_ctxtHandler == null || _provider==null) + throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured"); + + + } + + /** + * @return The default folder in which the context files of the osgi bundles + * are located and watched. Or null when the system property + * "jetty.osgi.contexts.home" is not defined. + * If the configuration file defines the OSGiAppProvider's context. + * This will not be taken into account. + */ + File getDefaultOSGiContextsHome(File jettyHome) + { + String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home"); + if (jettyContextsHome != null) + { + File contextsHome = new File(jettyContextsHome); + if (!contextsHome.exists() || !contextsHome.isDirectory()) + { + throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" + jettyContextsHome + " must exist and be a folder"); + } + return contextsHome; + } + return new File(jettyHome, "/contexts"); + } + + File getOSGiContextsHome() + { + return _provider.getContextXmlDirAsFile(); + } + + /** + * @return the urls in this string. + */ + private List<URL> extractResources(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List<URL> urls = new ArrayList<URL>(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + urls.add(((DefaultFileLocatorHelper) WebBundleDeployerHelper + .BUNDLE_FILE_LOCATOR_HELPER).getLocalURL(new URL(tok))); + } + catch (Throwable mfe) + { + + } + } + return urls; + } + + /** + * Get the folders that might contain jars for the legacy J2EE shared libraries + */ + private List<File> extractFiles(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List<File> files = new ArrayList<File>(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + URL url = new URL(tok); + url = ((DefaultFileLocatorHelper) WebBundleDeployerHelper + .BUNDLE_FILE_LOCATOR_HELPER).getFileURL(url); + if (url.getProtocol().equals("file")) + { + Resource res = Resource.newResource(url); + File folder = res.getFile(); + if (folder != null) + { + files.add(folder); + } + } + } + catch (Throwable mfe) + { + + } + } + return files; + } + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java new file mode 100644 index 0000000000..8805561480 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/IWebBundleDeployerHelper.java @@ -0,0 +1,84 @@ +// ======================================================================== +// Copyright (c) 2010 Intalio, 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 +// 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: +// Hugues Malphettes - initial API and implementation +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import org.eclipse.jetty.deploy.ContextDeployer; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.webapp.WebAppContext; +import org.osgi.framework.Bundle; + +/** + * Internal interface for the class that deploys a webapp on a server. + * Used as we migrate from the single instance of the jety server to multiple jetty servers. + */ +public interface IWebBundleDeployerHelper { + + /** when this property is present, the type of context handler registered is not + * known in advance. */ + public static final String INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE = "unknownContextHandlerType"; + + + /** + * Deploy a new web application on the jetty server. + * + * @param bundle + * The bundle + * @param webappFolderPath + * The path to the root of the webapp. Must be a path relative to + * bundle; either an absolute path. + * @param contextPath + * The context path. Must start with "/" + * @param extraClasspath + * @param overrideBundleInstallLocation + * @param webXmlPath + * @param defaultWebXmlPath + * TODO: parameter description + * @return The contexthandler created and started + * @throws Exception + */ + public abstract WebAppContext registerWebapplication(Bundle bundle, + String webappFolderPath, String contextPath, String extraClasspath, + String overrideBundleInstallLocation, String webXmlPath, + String defaultWebXmlPath, WebAppContext webAppContext) throws Exception; + + /** + * Stop a ContextHandler and remove it from the collection. + * + * @see ContextDeployer#undeploy + * @param contextHandler + * @throws Exception + */ + public abstract void unregister(ContextHandler contextHandler) + throws Exception; + + /** + * This type of registration relies on jetty's complete context xml file. + * Context encompasses jndi and all other things. This makes the definition + * of the webapp a lot more self-contained. + * + * @param contributor + * @param contextFileRelativePath + * @param extraClasspath + * @param overrideBundleInstallLocation + * @param handler the context handler passed in the server + * reference that will be configured, deployed and started. + * @return The contexthandler created and started + * @throws Exception + */ + public abstract ContextHandler registerContext(Bundle contributor, + String contextFileRelativePath, String extraClasspath, + String overrideBundleInstallLocation, ContextHandler handler) throws Exception; + +}
\ No newline at end of file diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java index 153267b1d8..efb908239a 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerServiceTracker.java @@ -13,12 +13,15 @@ package org.eclipse.jetty.osgi.boot.internal.webapp; import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; +import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; -import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.webapp.WebAppContext; @@ -49,62 +52,26 @@ import org.osgi.framework.ServiceReference; public class JettyContextHandlerServiceTracker implements ServiceListener { - private final WebappRegistrationHelper _helper; + /** New style: ability to manage multiple jetty instances */ + private final IManagedJettyServerRegistry _registry; /** The context-handler to deactivate indexed by context handler */ private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>(); /** - * The index is the bundle-symbolic-name/paht/to/context/file when there is - * such thing + * The index is the bundle-symbolic-name/path/to/context/file when there is such thing */ private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>(); - /** or null when */ - private String _osgiContextHomeFolderCanonicalPath; /** in charge of detecting changes in the osgi contexts home folder. */ private Scanner _scanner; /** - * @param context - * @param server + * @param registry */ - public JettyContextHandlerServiceTracker(BundleContext context, Server server) throws Exception + public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception { - _helper = new WebappRegistrationHelper(server); - _helper.setup(context,new HashMap<String, String>()); - File contextHome = _helper.getOSGiContextsHome(); - if (contextHome != null) - { - _osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath(); - _scanner = new Scanner(); - _scanner.setRecursive(true); - _scanner.setReportExistingFilesOnStartup(false); - _scanner.addListener(new Scanner.DiscreteListener() - { - public void fileAdded(String filename) throws Exception - { - // adding a file does not create a new app, - // it just reloads it with the new custom file. - // well, if the file does not define a context handler, - // then in fact it does remove it. - reloadJettyContextHandler(filename); - } - - public void fileChanged(String filename) throws Exception - { - reloadJettyContextHandler(filename); - } - - public void fileRemoved(String filename) throws Exception - { - // removing a file does not remove the app: - // it just goes back to the default embedded in the bundle. - // well, if there was no default then it does remove it. - reloadJettyContextHandler(filename); - } - }); - } + _registry = registry; } public void stop() @@ -117,6 +84,49 @@ public class JettyContextHandlerServiceTracker implements ServiceListener // nothing to stop in the WebappRegistrationHelper } + + /** + * @param contextHome Parent folder where the context files can override the context files + * defined in the web bundles: equivalent to the contexts folder in a traditional + * jetty installation. + * when null, just do nothing. + */ + protected void setupContextHomeScanner(File contextHome) throws IOException + { + if (contextHome == null) + { + return; + } + final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath(); + _scanner = new Scanner(); + _scanner.setRecursive(true); + _scanner.setReportExistingFilesOnStartup(false); + _scanner.addListener(new Scanner.DiscreteListener() + { + public void fileAdded(String filename) throws Exception + { + // adding a file does not create a new app, + // it just reloads it with the new custom file. + // well, if the file does not define a context handler, + // then in fact it does remove it. + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + + public void fileChanged(String filename) throws Exception + { + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + + public void fileRemoved(String filename) throws Exception + { + // removing a file does not remove the app: + // it just goes back to the default embedded in the bundle. + // well, if there was no default then it does remove it. + reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath); + } + }); + + } /** * Receives notification that a service has had a lifecycle change. @@ -137,7 +147,7 @@ public class JettyContextHandlerServiceTracker implements ServiceListener { try { - _helper.unregister(ctxtHandler); + getWebBundleDeployerHelp(sr).unregister(ctxtHandler); } catch (Exception e) { @@ -146,15 +156,15 @@ public class JettyContextHandlerServiceTracker implements ServiceListener } } } - if (ev.getType() == ServiceEvent.UNREGISTERING) - { - break; - } - else - { - // modified, meaning: we reload it. now that we stopped it; - // we can register it. - } + if (ev.getType() == ServiceEvent.UNREGISTERING) + { + break; + } + else + { + // modified, meaning: we reload it. now that we stopped it; + // we can register it. + } case ServiceEvent.REGISTERED: { Bundle contributor = sr.getBundle(); @@ -165,7 +175,11 @@ public class JettyContextHandlerServiceTracker implements ServiceListener // is configured elsewhere. return; } - if (contextHandler instanceof WebAppContext) + String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); + if (contextHandler instanceof WebAppContext && contextFilePath == null) + //it could be a web-application that will in fact be configured via a context file. + //that case is identified by the fact that the contextFilePath is not null + //in that case we must use the register context methods. { WebAppContext webapp = (WebAppContext)contextHandler; String contextPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH); @@ -186,13 +200,23 @@ public class JettyContextHandlerServiceTracker implements ServiceListener String war = (String)sr.getProperty("war"); try { - ContextHandler handler = _helper.registerWebapplication(contributor,war,contextPath,(String)sr + IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); + if (deployerHelper == null) + { + + } + else + { + WebAppContext handler = deployerHelper + .registerWebapplication(contributor,war,contextPath,(String)sr .getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),(String)sr - .getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),webXmlPath,defaultWebXmlPath); - if (handler != null) - { - registerInIndex(handler,sr); - } + .getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), + webXmlPath,defaultWebXmlPath,webapp); + if (handler != null) + { + registerInIndex(handler,sr); + } + } } catch (Throwable e) { @@ -202,20 +226,33 @@ public class JettyContextHandlerServiceTracker implements ServiceListener else { // consider this just an empty skeleton: - String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH); if (contextFilePath == null) { throw new IllegalArgumentException("the property contextFilePath is required"); } try { - ContextHandler handler = _helper.registerContext(contributor,contextFilePath,(String)sr - .getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),(String)sr - .getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE)); - if (handler != null) - { - registerInIndex(handler,sr); - } + IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr); + if (deployerHelper == null) + { + //more warnings? + } + else + { + if (Boolean.TRUE.toString().equals(sr.getProperty( + IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE))) + { + contextHandler = null; + } + ContextHandler handler = deployerHelper.registerContext(contributor,contextFilePath, + (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH), + (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE), + contextHandler); + if (handler != null) + { + registerInIndex(handler,sr); + } + } } catch (Throwable e) { @@ -279,9 +316,9 @@ public class JettyContextHandlerServiceTracker implements ServiceListener * * @param contextFileFully */ - void reloadJettyContextHandler(String canonicalNameOfFileChanged) + public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath) { - String key = getNormalizedRelativePath(canonicalNameOfFileChanged); + String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath); if (key == null) { return; @@ -299,16 +336,45 @@ public class JettyContextHandlerServiceTracker implements ServiceListener * @param canFilename * @return */ - private String getNormalizedRelativePath(String canFilename) + private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath) { - if (!canFilename.startsWith(_osgiContextHomeFolderCanonicalPath)) + if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath)) { // why are we here: this does not look like a child of the osgi // contexts home. // warning? return null; } - return canFilename.substring(_osgiContextHomeFolderCanonicalPath.length()).replace('\\','/'); + return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\','/'); } - + + /** + * @return The server on which this webapp is meant to be deployed + */ + private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName) + { + if (_registry == null) + { + return null; + } + if (managedServerName == null) + { + managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME; + } + ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName); + System.err.println("Returning " + managedServerName + " = " + wrapper); + return wrapper; + } + + private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr) + { + if (_registry == null) + { + return null; + } + String managedServerName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME); + ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName); + return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null; + } + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyHomeHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyHomeHelper.java deleted file mode 100644 index c6f7c3db49..0000000000 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyHomeHelper.java +++ /dev/null @@ -1,272 +0,0 @@ -// ======================================================================== -// Copyright (c) 2009 Intalio, 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 -// 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. -// ======================================================================== -package org.eclipse.jetty.osgi.boot.internal.webapp; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import org.eclipse.jetty.util.URIUtil; - -/** - * <p> - * Magically extract the jettyhome folder from this bundle's jar place it - * somewhere in the file-system. Currently we do this only when we detect a - * system property 'jetty.magic.home.parent' or if we are inside the pde in dev - * mode. In dev mode we use the osgi.configuration.area folder. - * </p> - * <p> - * This work is done through the jetty launch configuration inside the - * "Jetty Config" tab. We could choose to remove this code at some point - * although it does not hurt and it helps for users who don't go through the - * jetty launch. - * </p> - */ -class JettyHomeHelper -{ - - /** only magically extract jettyhome if we are inside the pde. */ - static boolean magic_install_only_in_pde = Boolean.valueOf(System.getProperty("jetty.magic.home.pde.only","true")); - - /** - * Hack for eclipse-PDE. When no jetty.home was set, detect if we running - * inside eclipse-PDE in development mode. In that case extract the - * jettyhome folder embedded inside this plugin inside the configuration - * area folder. It is specific to the workspace. Set the folder as - * jetty.home. If the folder already exist don't extract it again. - * <p> - * If we are not pde dev mode, the same but look in the installation folder - * of eclipse itself. - * </p> - * - * @return - * @throws URISyntaxException - */ - static String setupJettyHomeInEclipsePDE(File thisbundlejar) - { - File ecFolder = getParentFolderOfMagicHome(); - if (ecFolder == null || !ecFolder.exists()) - { - return null; - } - File jettyhome = new File(ecFolder,"jettyhome"); - String path; - try - { - path = jettyhome.getCanonicalPath(); - if (jettyhome.exists()) - { - System.setProperty("jetty.home",path); - return path; - } - else - { - // now grab the jar and unzip the relevant portion - unzipJettyHomeIntoDirectory(thisbundlejar,ecFolder); - System.setProperty("jetty.home",path); - return path; - } - } - catch (IOException e) - { - e.printStackTrace(); - } - return null; - } - - /** - * @return true when we are currently being run by the pde in development - * mode. - */ - private static boolean isPDEDevelopment() - { - String eclipseCommands = System.getProperty("eclipse.commands"); - // detect if we are being run from the pde: ie during development. - return eclipseCommands != null && eclipseCommands.indexOf("-dev") != -1 - && (eclipseCommands.indexOf("-dev\n") != -1 || eclipseCommands.indexOf("-dev\r") != -1 || eclipseCommands.indexOf("-dev ") != -1); - } - - /** - * @return - */ - private static File getConfigurationAreaDirectory() - { - return getFile(System.getProperty("osgi.configuration.area")); - } - - /** - * @param zipFile - * The current jar file for this bundle. contains an archive of - * the default jettyhome - * @param parentOfMagicJettyHome - * The folder inside which jettyhome is created. - */ - private static void unzipJettyHomeIntoDirectory(File thisbundlejar, File parentOfMagicJettyHome) throws IOException - { - ZipFile zipFile = null; - try - { - zipFile = new ZipFile(thisbundlejar); - Enumeration<? extends ZipEntry> files = zipFile.entries(); - File f = null; - FileOutputStream fos = null; - - while (files.hasMoreElements()) - { - try - { - ZipEntry entry = files.nextElement(); - String entryName = entry.getName(); - if (!entryName.startsWith("jettyhome")) - { - continue; - } - - InputStream eis = zipFile.getInputStream(entry); - byte[] buffer = new byte[1024]; - int bytesRead = 0; - f = new File(parentOfMagicJettyHome,entry.getName()); - - if (entry.isDirectory()) - { - f.mkdirs(); - } - else - { - f.getParentFile().mkdirs(); - f.createNewFile(); - fos = new FileOutputStream(f); - while ((bytesRead = eis.read(buffer)) != -1) - { - fos.write(buffer,0,bytesRead); - } - } - } - catch (IOException e) - { - e.printStackTrace(); - continue; - } - finally - { - if (fos != null) - { - try - { - fos.close(); - } - catch (IOException e) - { - } - fos = null; - } - } - } - } - finally - { - if (zipFile != null) - try - { - zipFile.close(); - } - catch (Throwable t) - { - } - } - } - - /** - * Look for the parent folder that contains jettyhome. Can be specified by - * the sys property jetty.magic.home.parent or if inside the pde will - * default on the configuration area. Otherwise returns null. - * - * @return The folder inside which jettyhome should be placed. - */ - private static File getParentFolderOfMagicHome() - { - // for (java.util.Map.Entry<Object, Object> e : - // System.getProperties().entrySet()) { - // System.err.println(e.getKey() + " -> " + e.getValue()); - // } - String magicParent = WebappRegistrationHelper.stripQuotesIfPresent(System.getProperty("jetty.magic.home.parent")); - String magicParentValue = magicParent != null?System.getProperty(magicParent):null; - File specifiedMagicParent = magicParentValue != null?getFile(magicParentValue) // in - // that - // case - // it - // was - // pointing - // to - // another - // system - // property. - :getFile(magicParent); // in that case it was directly a file. - if (specifiedMagicParent != null && specifiedMagicParent.exists()) - { - return specifiedMagicParent; - } - if (isPDEDevelopment()) - { - return getConfigurationAreaDirectory(); - } - return null; - } - - /** - * Be flexible with the url/uri/path that can be the value of the various - * system properties. - * - * @param file - * @return a file. might not exist. - */ - private static File getFile(String file) - { - if (file == null) - { - return null; - } - file = WebappRegistrationHelper.stripQuotesIfPresent(file); - try - { - if (file.startsWith("file:/")) - { - if (!file.startsWith("file://")) - { - return new File(new URI(URIUtil.encodePath(file))); - } - else - { - return new File(new URL(file).toURI()); - } - } - else - { - return new File(file); - } - } - catch (Throwable t) - { - t.printStackTrace(); - return new File(file); - } - - } -} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java index e93585277d..ee5e3c97de 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java @@ -19,6 +19,7 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -91,9 +92,13 @@ public class LibExtClassLoaderHelper * is the JettyBootStrapper (an osgi classloader. * @throws MalformedURLException */ - public static URLClassLoader createLibEtcClassLoaderHelper(File jettyHome, Server server, ClassLoader parentClassLoader) throws MalformedURLException + public static ClassLoader createLibEtcClassLoader(File jettyHome, Server server, + ClassLoader parentClassLoader) throws MalformedURLException { - + if (jettyHome == null) + { + return parentClassLoader; + } ArrayList<URL> urls = new ArrayList<URL>(); File jettyResources = new File(jettyHome,"resources"); if (jettyResources.exists()) @@ -139,6 +144,52 @@ public class LibExtClassLoaderHelper } /** + * @param server + * @return a url classloader with the jars of resources, lib/ext and the + * jars passed in the other argument. The parent classloader usually + * is the JettyBootStrapper (an osgi classloader). + * If there was no extra jars to insert, then just return the parentClassLoader. + * @throws MalformedURLException + */ + public static ClassLoader createLibExtClassLoader(List<File> jarsContainerOrJars, + List<URL> otherJarsOrFolder, Server server, + ClassLoader parentClassLoader) throws MalformedURLException + { + if (jarsContainerOrJars == null && otherJarsOrFolder == null) + { + return parentClassLoader; + } + List<URL> urls = new ArrayList<URL>(); + if (otherJarsOrFolder != null) + { + urls.addAll(otherJarsOrFolder); + } + if (jarsContainerOrJars != null) + { + for (File libExt : jarsContainerOrJars) + { + if (libExt.isDirectory()) + { + for (File f : libExt.listFiles()) + { + if (f.getName().endsWith(".jar")) + { + // cheap to tolerate folders so let's do it. + URL url = f.toURI().toURL(); + if (f.isFile()) + {// is this necessary anyways? + url = new URL("jar:" + url.toString() + "!/"); + } + urls.add(url); + } + } + } + } + } + return new URLClassLoader(urls.toArray(new URL[urls.size()]),parentClassLoader); + } + + /** * When we find files typically used for central logging configuration we do * what it takes in this method to do what the user expects. Without * depending too much directly on a particular logging framework. diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java index 50cec93fda..13c097e85e 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/OSGiWebappClassLoader.java @@ -29,18 +29,20 @@ import java.util.jar.JarFile; import javax.servlet.http.HttpServlet; +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; import org.osgi.framework.Bundle; +import org.osgi.framework.BundleReference; /** * Extends the webappclassloader to insert the classloader provided by the osgi * bundle at the same level than any other jars palced in the webappclassloader. */ -public class OSGiWebappClassLoader extends WebAppClassLoader +public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference { private Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName().toString()); @@ -67,14 +69,34 @@ public class OSGiWebappClassLoader extends WebAppClassLoader } private ClassLoader _osgiBundleClassLoader; + private Bundle _contributor; private boolean _lookInOsgiFirst = true; private Set<String> _libsAlreadyInManifest = new HashSet<String>(); - public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor) throws IOException + /** + * @param parent The parent classloader. In this case + * @param context The WebAppContext + * @param contributor The bundle that defines this web-application. + * @throws IOException + */ + public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor, + BundleClassLoaderHelper bundleClassLoaderHelper) throws IOException { super(parent,context); - _osgiBundleClassLoader = WebappRegistrationHelper.BUNDLE_CLASS_LOADER_HELPER.getBundleClassLoader(contributor); + _contributor = contributor; + _osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor); } + + /** + * Returns the <code>Bundle</code> that defined this web-application. + * + * @return The <code>Bundle</code> object associated with this + * <code>BundleReference</code>. + */ + public Bundle getBundle() + { + return _contributor; + } /** * Reads the manifest. If the manifest is already configured to loads a few diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java new file mode 100644 index 0000000000..f43dbb31df --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleDeployerHelper.java @@ -0,0 +1,619 @@ +// ======================================================================== +// Copyright (c) 2009 Intalio, 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 +// 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: +// Hugues Malphettes - initial API and implementation +// ======================================================================== +package org.eclipse.jetty.osgi.boot.internal.webapp; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; + +import org.eclipse.jetty.deploy.ContextDeployer; +import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; +import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper; +import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper; +import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.xml.sax.SAXException; + +/** + * Bridges the jetty deployers with the OSGi lifecycle where applications are + * managed inside OSGi-bundles. + * <p> + * This class should be called as a consequence of the activation of a new + * service that is a ContextHandler.<br/> + * This way the new webapps are exposed as OSGi services. + * </p> + * <p> + * Helper methods to register a bundle that is a web-application or a context. + * </p> + * Limitations: + * <ul> + * <li>support for jarred webapps is somewhat limited.</li> + * </ul> + */ +public class WebBundleDeployerHelper implements IWebBundleDeployerHelper +{ + + private static Logger __logger = Log.getLogger(WebBundleDeployerHelper.class.getName()); + + private static boolean INITIALIZED = false; + + /** + * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports + * equinox and apache-felix fragment bundles that are specific to an OSGi + * implementation should set a different implementation. + */ + public static BundleClassLoaderHelper BUNDLE_CLASS_LOADER_HELPER = null; + /** + * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports + * equinox and apache-felix fragment bundles that are specific to an OSGi + * implementation should set a different implementation. + */ + public static BundleFileLocatorHelper BUNDLE_FILE_LOCATOR_HELPER = null; + + /** + * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports + * equinox and apache-felix fragment bundles that are specific to an OSGi + * implementation should set a different implementation. + * <p> + * Several of those objects can be added here: For example we could have an optional fragment that setups + * a specific implementation of JSF for the whole of jetty-osgi. + * </p> + */ + public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>(); + + /** + * this class loader loads the jars inside {$jetty.home}/lib/ext it is meant + * as a migration path and for jars that are not OSGi ready. also gives + * access to the jsp jars. + */ + // private URLClassLoader _libExtClassLoader; + + private ServerInstanceWrapper _wrapper; + + public WebBundleDeployerHelper(ServerInstanceWrapper wrapper) + { + staticInit(); + _wrapper = wrapper; + } + + // Inject the customizing classes that might be defined in fragment bundles. + public static synchronized void staticInit() + { + if (!INITIALIZED) + { + INITIALIZED = true; + // setup the custom BundleClassLoaderHelper + try + { + BUNDLE_CLASS_LOADER_HELPER = (BundleClassLoaderHelper)Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + // System.err.println("support for equinox and felix"); + BUNDLE_CLASS_LOADER_HELPER = new DefaultBundleClassLoaderHelper(); + } + // setup the custom FileLocatorHelper + try + { + BUNDLE_FILE_LOCATOR_HELPER = (BundleFileLocatorHelper)Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance(); + } + catch (Throwable t) + { + // System.err.println("no jsp/jasper support"); + BUNDLE_FILE_LOCATOR_HELPER = new DefaultFileLocatorHelper(); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#registerWebapplication(org.osgi.framework.Bundle, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public WebAppContext registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath, + String overrideBundleInstallLocation, String webXmlPath, String defaultWebXmlPath, WebAppContext webAppContext) throws Exception + { + File bundleInstall = overrideBundleInstallLocation == null?BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle):new File( + overrideBundleInstallLocation); + File webapp = null; + URL baseWebappInstallURL = null; + if (webappFolderPath != null && webappFolderPath.length() != 0 && !webappFolderPath.equals(".")) + { + if (webappFolderPath.startsWith("/") || webappFolderPath.startsWith("file:")) + { + webapp = new File(webappFolderPath); + } + else if (bundleInstall != null && bundleInstall.isDirectory()) + { + webapp = new File(bundleInstall,webappFolderPath); + } + else if (bundleInstall != null) + { + Enumeration<URL> urls = BUNDLE_FILE_LOCATOR_HELPER.findEntries(bundle, webappFolderPath); + if (urls != null && urls.hasMoreElements()) + { + baseWebappInstallURL = urls.nextElement(); + } + } + } + else + { + webapp = bundleInstall; + } + if (baseWebappInstallURL == null && (webapp == null || !webapp.exists())) + { + throw new IllegalArgumentException("Unable to locate " + webappFolderPath + " inside " + + (bundleInstall != null?bundleInstall.getAbsolutePath():"unlocated bundle '" + bundle.getSymbolicName() + "'")); + } + if (baseWebappInstallURL == null && webapp != null) + { + baseWebappInstallURL = webapp.toURI().toURL(); + } + return registerWebapplication(bundle,webappFolderPath,baseWebappInstallURL,contextPath, + extraClasspath,bundleInstall,webXmlPath,defaultWebXmlPath,webAppContext); + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#registerWebapplication(org.osgi.framework.Bundle, java.lang.String, java.io.File, java.lang.String, java.lang.String, java.io.File, java.lang.String, java.lang.String) + */ + private WebAppContext registerWebapplication(Bundle contributor, String pathInBundleToWebApp, + URL baseWebappInstallURL, String contextPath, String extraClasspath, File bundleInstall, + String webXmlPath, String defaultWebXmlPath, WebAppContext context) throws Exception + { + + ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); + String[] oldServerClasses = null; + + try + { + // make sure we provide access to all the jetty bundles by going + // through this bundle. + OSGiWebappClassLoader composite = createWebappClassLoader(contributor); + // configure with access to all jetty classes and also all the classes + // that the contributor gives access to. + Thread.currentThread().setContextClassLoader(composite); + + context.setWar(baseWebappInstallURL.toString()); + context.setContextPath(contextPath); + context.setExtraClasspath(extraClasspath); + + if (webXmlPath != null && webXmlPath.length() != 0) + { + File webXml = null; + if (webXmlPath.startsWith("/") || webXmlPath.startsWith("file:/")) + { + webXml = new File(webXmlPath); + } + else + { + webXml = new File(bundleInstall,webXmlPath); + } + if (webXml.exists()) + { + context.setDescriptor(webXml.getAbsolutePath()); + } + } + + if (defaultWebXmlPath == null || defaultWebXmlPath.length() == 0) + { + //use the one defined by the OSGiAppProvider. + defaultWebXmlPath = _wrapper.getOSGiAppProvider().getDefaultsDescriptor(); + } + if (defaultWebXmlPath != null && defaultWebXmlPath.length() != 0) + { + File defaultWebXml = null; + if (defaultWebXmlPath.startsWith("/") || defaultWebXmlPath.startsWith("file:/")) + { + defaultWebXml = new File(webXmlPath); + } + else + { + defaultWebXml = new File(bundleInstall,defaultWebXmlPath); + } + if (defaultWebXml.exists()) + { + context.setDefaultsDescriptor(defaultWebXml.getAbsolutePath()); + } + } + + //other parameters that might be defines on the OSGiAppProvider: + context.setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority()); + + configureWebAppContext(context,contributor); + configureWebappClassLoader(contributor,context,composite); + + // @see + // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) + // during initialization of the webapp all the jetty packages are + // visible + // through the webapp classloader. + oldServerClasses = context.getServerClasses(); + context.setServerClasses(null); + + _wrapper.getOSGiAppProvider().addContext(contributor,pathInBundleToWebApp,context); + + return context; + } + finally + { + if (context != null && oldServerClasses != null) + { + context.setServerClasses(oldServerClasses); + } + Thread.currentThread().setContextClassLoader(contextCl); + } + + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#unregister(org.eclipse.jetty.server.handler.ContextHandler) + */ + public void unregister(ContextHandler contextHandler) throws Exception + { + _wrapper.getOSGiAppProvider().removeContext(contextHandler); + } + + /* (non-Javadoc) + * @see org.eclipse.jetty.osgi.boot.internal.webapp.IWebBundleDeployerHelper#registerContext(org.osgi.framework.Bundle, java.lang.String, java.lang.String, java.lang.String) + */ + public ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, + String overrideBundleInstallLocation, ContextHandler handler) + throws Exception + { + File contextsHome = _wrapper.getOSGiAppProvider().getContextXmlDirAsFile(); + if (contextsHome != null) + { + File prodContextFile = new File(contextsHome,contributor.getSymbolicName() + "/" + contextFileRelativePath); + if (prodContextFile.exists()) + { + return registerContext(contributor,contextFileRelativePath,prodContextFile,extraClasspath, + overrideBundleInstallLocation,handler); + } + } + File rootFolder = overrideBundleInstallLocation != null + ? Resource.newResource(overrideBundleInstallLocation).getFile() + : BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(contributor); + File contextFile = rootFolder != null?new File(rootFolder,contextFileRelativePath):null; + if (contextFile != null && contextFile.exists()) + { + return registerContext(contributor,contextFileRelativePath,contextFile,extraClasspath,overrideBundleInstallLocation,handler); + } + else + { + if (contextFileRelativePath.startsWith("./")) + { + contextFileRelativePath = contextFileRelativePath.substring(1); + } + if (!contextFileRelativePath.startsWith("/")) + { + contextFileRelativePath = "/" + contextFileRelativePath; + } + + URL contextURL = contributor.getEntry(contextFileRelativePath); + if (contextURL != null) + { + return registerContext(contributor,contextFileRelativePath,contextURL.openStream(),extraClasspath,overrideBundleInstallLocation,handler); + } + throw new IllegalArgumentException("Could not find the context " + "file " + contextFileRelativePath + " for the bundle " + + contributor.getSymbolicName() + (overrideBundleInstallLocation != null?" using the install location " + overrideBundleInstallLocation:"")); + } + } + + /** + * This type of registration relies on jetty's complete context xml file. + * Context encompasses jndi and all other things. This makes the definition + * of the webapp a lot more self-contained. + * + * @param webapp + * @param contextPath + * @param classInBundle + * @throws Exception + */ + private ContextHandler registerContext(Bundle contributor, String pathInBundle, File contextFile, + String extraClasspath, String overrideBundleInstallLocation, ContextHandler handler) throws Exception + { + InputStream contextFileInputStream = null; + try + { + contextFileInputStream = new BufferedInputStream(new FileInputStream(contextFile)); + return registerContext(contributor, pathInBundle, contextFileInputStream,extraClasspath,overrideBundleInstallLocation, handler); + } + finally + { + IO.close(contextFileInputStream); + } + } + + /** + * @param contributor + * @param contextFileInputStream + * @return The ContextHandler created and registered or null if it did not + * happen. + * @throws Exception + */ + private ContextHandler registerContext(Bundle contributor, String pathInsideBundle, InputStream contextFileInputStream, + String extraClasspath, String overrideBundleInstallLocation, ContextHandler handler) + throws Exception + { + ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); + String[] oldServerClasses = null; + WebAppContext webAppContext = null; + try + { + // make sure we provide access to all the jetty bundles by going + // through this bundle. + OSGiWebappClassLoader composite = createWebappClassLoader(contributor); + // configure with access to all jetty classes and also all the + // classes + // that the contributor gives access to. + Thread.currentThread().setContextClassLoader(composite); + ContextHandler context = createContextHandler(handler, contributor,contextFileInputStream,extraClasspath,overrideBundleInstallLocation); + if (context == null) + { + return null;// did not happen + } + + // ok now register this webapp. we checked when we started jetty + // that there + // was at least one such handler for webapps. + //the actual registration must happen via the new Deployment API. +// _ctxtHandler.addHandler(context); + + configureWebappClassLoader(contributor,context,composite); + if (context instanceof WebAppContext) + { + webAppContext = (WebAppContext)context; + // @see + // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) + oldServerClasses = webAppContext.getServerClasses(); + webAppContext.setServerClasses(null); + } + _wrapper.getOSGiAppProvider().addContext(contributor, pathInsideBundle, context); + return context; + } + finally + { + if (webAppContext != null) + { + webAppContext.setServerClasses(oldServerClasses); + } + Thread.currentThread().setContextClassLoader(contextCl); + } + + } + + /** + * Applies the properties of WebAppDeployer as defined in jetty.xml. + * + * @see {WebAppDeployer#scan} around the comment + * <code>// configure it</code> + */ + protected void configureWebAppContext(WebAppContext wah, Bundle contributor) + { + // rfc66 + wah.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,contributor.getBundleContext()); + + //spring-dm-1.2.1 looks for the BundleContext as a different attribute. + //not a spec... but if we want to support + //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext + //then we need to do this to: + wah.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), + contributor.getBundleContext()); + + } + + /** + * @See {@link ContextDeployer#scan} + * @param contextFile + * @return + */ + protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, + Bundle bundle, File contextFile, String extraClasspath, String overrideBundleInstallLocation) + { + try + { + return createContextHandler(handlerToConfigure,bundle,new BufferedInputStream(new FileInputStream(contextFile)),extraClasspath,overrideBundleInstallLocation); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + return null; + } + + /** + * @See {@link ContextDeployer#scan} + * @param contextFile + * @return + */ + @SuppressWarnings("unchecked") + protected ContextHandler createContextHandler(ContextHandler handlerToConfigure, + Bundle bundle, InputStream contextInputStream, String extraClasspath, String overrideBundleInstallLocation) + { + /* + * Do something identical to what the ContextProvider would have done: + * XmlConfiguration xmlConfiguration=new + * XmlConfiguration(resource.getURL()); HashMap properties = new + * HashMap(); properties.put("Server", _contexts.getServer()); if + * (_configMgr!=null) properties.putAll(_configMgr.getProperties()); + * + * xmlConfiguration.setProperties(properties); ContextHandler + * context=(ContextHandler)xmlConfiguration.configure(); + * context.setAttributes(new AttributesMap(_contextAttributes)); + */ + try + { + XmlConfiguration xmlConfiguration = new XmlConfiguration(contextInputStream); + HashMap properties = new HashMap(); + properties.put("Server",_wrapper.getServer()); + + // insert the bundle's location as a property. + setThisBundleHomeProperty(bundle,properties,overrideBundleInstallLocation); + xmlConfiguration.setProperties(properties); + + ContextHandler context = null; + if (handlerToConfigure == null) + { + context = (ContextHandler)xmlConfiguration.configure(); + } + else + { + xmlConfiguration.configure(handlerToConfigure); + context = handlerToConfigure; + } + + if (context instanceof WebAppContext) + { + ((WebAppContext)context).setExtraClasspath(extraClasspath); + ((WebAppContext)context).setParentLoaderPriority(_wrapper.getOSGiAppProvider().isParentLoaderPriority()); + if (_wrapper.getOSGiAppProvider().getDefaultsDescriptor() != null && _wrapper.getOSGiAppProvider().getDefaultsDescriptor().length() != 0) + { + ((WebAppContext)context).setDefaultsDescriptor(_wrapper.getOSGiAppProvider().getDefaultsDescriptor()); + } + } + + // rfc-66: + context.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,bundle.getBundleContext()); + + //spring-dm-1.2.1 looks for the BundleContext as a different attribute. + //not a spec... but if we want to support + //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext + //then we need to do this to: + context.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), + bundle.getBundleContext()); + return context; + } + catch (FileNotFoundException e) + { + return null; + } + catch (SAXException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (Throwable e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + finally + { + IO.close(contextInputStream); + } + return null; + } + + /** + * Configure a classloader onto the context. If the context is a + * WebAppContext, build a WebAppClassLoader that has access to all the jetty + * classes thanks to the classloader of the JettyBootStrapper bundle and + * also has access to the classloader of the bundle that defines this + * context. + * <p> + * If the context is not a WebAppContext, same but with a simpler + * URLClassLoader. Note that the URLClassLoader is pretty much fake: it + * delegate all actual classloading to the parent classloaders. + * </p> + * <p> + * The URL[] returned by the URLClassLoader create contained specifically + * the jars that some j2ee tools expect and look into. For example the jars + * that contain tld files for jasper's jstl support. + * </p> + * <p> + * Also as the jars in the lib folder and the classes in the classes folder + * might already be in the OSGi classloader we filter them out of the + * WebAppClassLoader + * </p> + * + * @param context + * @param contributor + * @param webapp + * @param contextPath + * @param classInBundle + * @throws Exception + */ + protected void configureWebappClassLoader(Bundle contributor, ContextHandler context, OSGiWebappClassLoader webappClassLoader) throws Exception + { + if (context instanceof WebAppContext) + { + WebAppContext webappCtxt = (WebAppContext)context; + context.setClassLoader(webappClassLoader); + webappClassLoader.setWebappContext(webappCtxt); + } + else + { + context.setClassLoader(webappClassLoader); + } + } + + /** + * No matter what the type of webapp, we create a WebappClassLoader. + */ + protected OSGiWebappClassLoader createWebappClassLoader(Bundle contributor) throws Exception + { + // we use a temporary WebAppContext object. + // if this is a real webapp we will set it on it a bit later: once we + // know. + OSGiWebappClassLoader webappClassLoader = new OSGiWebappClassLoader( + _wrapper.getParentClassLoaderForWebapps(),new WebAppContext(),contributor,BUNDLE_CLASS_LOADER_HELPER); + return webappClassLoader; + } + + /** + * Set the property "this.bundle.install" to point to the location + * of the bundle. Useful when <SystemProperty name="this.bundle.home"/> is + * used. + */ + private void setThisBundleHomeProperty(Bundle bundle, HashMap<String, Object> properties, String overrideBundleInstallLocation) + { + try + { + File location = overrideBundleInstallLocation != null?new File(overrideBundleInstallLocation):BUNDLE_FILE_LOCATOR_HELPER + .getBundleInstallLocation(bundle); + properties.put("this.bundle.install",location.getCanonicalPath()); + properties.put("this.bundle.install.url",bundle.getEntry("/").toString()); + } + catch (Throwable t) + { + System.err.println("Unable to set 'this.bundle.install' " + " for the bundle " + bundle.getSymbolicName()); + t.printStackTrace(); + } + } + + +} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerExtender.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java index b82bd29050..df257839fd 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/JettyContextHandlerExtender.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebBundleTrackerCustomizer.java @@ -1,5 +1,5 @@ // ======================================================================== -// Copyright (c) 2009 Intalio, Inc. +// Copyright (c) 2009-2010 Intalio, Inc. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 @@ -18,9 +18,10 @@ import java.util.Dictionary; import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; -import org.osgi.framework.BundleListener; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; + /** * Support bundles that declare the webapp directly through headers in their @@ -46,44 +47,102 @@ import org.osgi.framework.BundleListener; * * @author hmalphettes */ -public class JettyContextHandlerExtender implements BundleListener -{ +public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { + - /** - * Receives notification that a bundle has had a lifecycle change. - * - * @param event - * The <code>BundleEvent</code>. - */ - public void bundleChanged(BundleEvent event) - { - switch (event.getType()) - { - case BundleEvent.STARTED: - register(event.getBundle()); - break; - case BundleEvent.STOPPING: - unregister(event.getBundle()); - break; - } - } + /** + * A bundle is being added to the <code>BundleTracker</code>. + * + * <p> + * This method is called before a bundle which matched the search parameters + * of the <code>BundleTracker</code> is added to the + * <code>BundleTracker</code>. This method should return the object to be + * tracked for the specified <code>Bundle</code>. The returned object is + * stored in the <code>BundleTracker</code> and is available from the + * {@link BundleTracker#getObject(Bundle) getObject} method. + * + * @param bundle The <code>Bundle</code> being added to the + * <code>BundleTracker</code>. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event associated + * with the call to this method. + * @return The object to be tracked for the specified <code>Bundle</code> + * object or <code>null</code> if the specified <code>Bundle</code> + * object should not be tracked. + */ + public Object addingBundle(Bundle bundle, BundleEvent event) + { + if (bundle.getState() == Bundle.ACTIVE) + { + boolean isWebBundle = register(bundle); + return isWebBundle ? bundle : null; + } + else if (bundle.getState() == Bundle.STOPPING) + { + unregister(bundle); + } + else + { + //we should not be called in that state as + //we are registered only for ACTIVE and STOPPING + } + return null; + } - /** + /** + * A bundle tracked by the <code>BundleTracker</code> has been modified. * + * <p> + * This method is called when a bundle being tracked by the + * <code>BundleTracker</code> has had its state modified. + * + * @param bundle The <code>Bundle</code> whose state has been modified. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event associated + * with the call to this method. + * @param object The tracked object for the specified bundle. */ - public void init(BundleContext context) - { - Bundle bundles[] = context.getBundles(); - for (int i = 0; i < bundles.length; i++) - { - if ((bundles[i].getState() & (Bundle.STARTING | Bundle.ACTIVE)) != 0) - { - register(bundles[i]); - } - } - } + public void modifiedBundle(Bundle bundle, BundleEvent event, + Object object) + { + //nothing the web-bundle was already track. something changed. + //we only reload the webapps if the bundle is stopped and restarted. +// System.err.println(bundle.getSymbolicName()); + if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE) + { + unregister(bundle); + } + if (bundle.getState() == Bundle.ACTIVE) + { + register(bundle); + } + } - private void register(Bundle bundle) + /** + * A bundle tracked by the <code>BundleTracker</code> has been removed. + * + * <p> + * This method is called after a bundle is no longer being tracked by the + * <code>BundleTracker</code>. + * + * @param bundle The <code>Bundle</code> that has been removed. + * @param event The bundle event which caused this customizer method to be + * called or <code>null</code> if there is no bundle event associated + * with the call to this method. + * @param object The tracked object for the specified bundle. + */ + public void removedBundle(Bundle bundle, BundleEvent event, + Object object) + { + unregister(bundle); + } + + + /** + * @param bundle + * @return true if this bundle in indeed a web-bundle. + */ + private boolean register(Bundle bundle) { Dictionary<?, ?> dic = bundle.getHeaders(); String warFolderRelativePath = (String)dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); @@ -99,11 +158,13 @@ public class JettyContextHandlerExtender implements BundleListener try { JettyBootstrapActivator.registerWebapplication(bundle,warFolderRelativePath,contextPath); + return true; } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); + return true;//maybe it did not work maybe it did. safer to track this bundle. } } else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null) @@ -112,7 +173,7 @@ public class JettyContextHandlerExtender implements BundleListener if (contextFileRelativePath == null) { // nothing to register here. - return; + return false; } // support for multiple webapps in the same bundle: String[] pathes = contextFileRelativePath.split(",;"); @@ -128,6 +189,7 @@ public class JettyContextHandlerExtender implements BundleListener e.printStackTrace(); } } + return true; } else { @@ -137,7 +199,7 @@ public class JettyContextHandlerExtender implements BundleListener URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml"); if (rfc66Webxml == null) { - return;// no webapp in here + return false;// no webapp in here } // this is risky: should we make sure that there is no classes and // jars directly available @@ -151,11 +213,13 @@ public class JettyContextHandlerExtender implements BundleListener try { JettyBootstrapActivator.registerWebapplication(bundle,".",rfc66ContextPath); + return true; } catch (Throwable e) { // TODO Auto-generated catch block e.printStackTrace(); + return true;//maybe it did not work maybe it did. safer to track this bundle. } } } @@ -195,4 +259,7 @@ public class JettyContextHandlerExtender implements BundleListener // webapps registered in that bundle. } + + + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebappRegistrationHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebappRegistrationHelper.java deleted file mode 100644 index 447893d315..0000000000 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/WebappRegistrationHelper.java +++ /dev/null @@ -1,979 +0,0 @@ -// ======================================================================== -// Copyright (c) 2009 Intalio, 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 -// 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: -// Hugues Malphettes - initial API and implementation -// ======================================================================== -package org.eclipse.jetty.osgi.boot.internal.webapp; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; - -import org.eclipse.jetty.deploy.AppProvider; -import org.eclipse.jetty.deploy.ContextDeployer; -import org.eclipse.jetty.deploy.DeploymentManager; -import org.eclipse.jetty.deploy.WebAppDeployer; -import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; -import org.eclipse.jetty.osgi.boot.OSGiAppProvider; -import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; -import org.eclipse.jetty.osgi.boot.internal.jsp.TldLocatableURLClassloader; -import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; -import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultBundleClassLoaderHelper; -import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.server.handler.ContextHandlerCollection; -import org.eclipse.jetty.server.handler.DefaultHandler; -import org.eclipse.jetty.server.handler.HandlerCollection; -import org.eclipse.jetty.server.handler.RequestLogHandler; -import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.webapp.WebAppContext; -import org.eclipse.jetty.xml.XmlConfiguration; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -/** - * Bridges the jetty deployers with the OSGi lifecycle where applications are - * managed inside OSGi-bundles. - * <p> - * This class should be called as a consequence of the activation of a new - * service that is a ContextHandler.<br/> - * This way the new webapps are exposed as OSGi services. - * </p> - * <p> - * Helper methods to register a bundle that is a web-application or a context. - * </p> - * Limitations: - * <ul> - * <li>support for jarred webapps is somewhat limited.</li> - * </ul> - */ -public class WebappRegistrationHelper -{ - - private static Logger __logger = Log.getLogger(WebappRegistrationHelper.class.getName()); - - private static boolean INITIALIZED = false; - - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - */ - public static BundleClassLoaderHelper BUNDLE_CLASS_LOADER_HELPER = null; - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - */ - public static BundleFileLocatorHelper BUNDLE_FILE_LOCATOR_HELPER = null; - - /** - * By default set to: {@link DefaultBundleClassLoaderHelper}. It supports - * equinox and apache-felix fragment bundles that are specific to an OSGi - * implementation should set a different implementation. - * <p> - * Several of those objects can be added here: For example we could have an optional fragment that setups - * a specific implementation of JSF for the whole of jetty-osgi. - * </p> - */ - public static Collection<WebappRegistrationCustomizer> JSP_REGISTRATION_HELPERS = new ArrayList<WebappRegistrationCustomizer>(); - - private Server _server; - private ContextHandlerCollection _ctxtHandler; - - /** - * this class loader loads the jars inside {$jetty.home}/lib/ext it is meant - * as a migration path and for jars that are not OSGi ready. also gives - * access to the jsp jars. - */ - // private URLClassLoader _libExtClassLoader; - - /** - * This is the class loader that should be the parent classloader of any - * webapp classloader. It is in fact the _libExtClassLoader with a trick to - * let the TldScanner find the jars where the tld files are. - */ - private URLClassLoader _commonParentClassLoaderForWebapps; - - private DeploymentManager _deploymentManager; - - private OSGiAppProvider _provider; - - public WebappRegistrationHelper(Server server) - { - _server = server; - staticInit(); - } - - // Inject the customizing classes that might be defined in fragment bundles. - private static synchronized void staticInit() - { - if (!INITIALIZED) - { - INITIALIZED = true; - // setup the custom BundleClassLoaderHelper - try - { - BUNDLE_CLASS_LOADER_HELPER = (BundleClassLoaderHelper)Class.forName(BundleClassLoaderHelper.CLASS_NAME).newInstance(); - } - catch (Throwable t) - { - // System.err.println("support for equinox and felix"); - BUNDLE_CLASS_LOADER_HELPER = new DefaultBundleClassLoaderHelper(); - } - // setup the custom FileLocatorHelper - try - { - BUNDLE_FILE_LOCATOR_HELPER = (BundleFileLocatorHelper)Class.forName(BundleFileLocatorHelper.CLASS_NAME).newInstance(); - } - catch (Throwable t) - { - // System.err.println("no jsp/jasper support"); - BUNDLE_FILE_LOCATOR_HELPER = new DefaultFileLocatorHelper(); - } - } - } - - /** - * Removes quotes around system property values before we try to make them - * into file pathes. - */ - public static String stripQuotesIfPresent(String filePath) - { - if (filePath == null) - return null; - - if ((filePath.startsWith("\"") || filePath.startsWith("'")) && (filePath.endsWith("\"") || filePath.endsWith("'"))) - return filePath.substring(1,filePath.length() - 1); - return filePath; - } - - /** - * Look for the home directory of jetty as defined by the system property - * 'jetty.home'. If undefined, look at the current bundle and uses its own - * jettyhome folder for this feature. - * <p> - * Special case: inside eclipse-SDK:<br/> - * If the bundle is jarred, see if we are inside eclipse-PDE itself. In that - * case, look for the installation directory of eclipse-PDE, try to create a - * jettyhome folder there and install the sample jettyhome folder at that - * location. This makes the installation in eclipse-SDK easier. <br/> - * This is a bit redundant with the work done by the jetty configuration - * launcher. - * </p> - * - * @param context - * @throws Exception - */ - public void setup(BundleContext context, Map<String, String> configProperties) throws Exception - { - File _installLocation = BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(context.getBundle()); - // debug: - // new File("~/proj/eclipse-install/eclipse-3.5.1-SDK-jetty7/" + - // "dropins/jetty7/plugins/org.eclipse.jetty.osgi.boot_0.0.1.001-SNAPSHOT.jar"); - boolean bootBundleCanBeJarred = true; - String jettyHome = stripQuotesIfPresent(System.getProperty("jetty.home")); - - if (jettyHome == null || jettyHome.length() == 0) - { - if (_installLocation.getName().endsWith(".jar")) - { - jettyHome = JettyHomeHelper.setupJettyHomeInEclipsePDE(_installLocation); - } - if (jettyHome == null) - { - jettyHome = _installLocation.getAbsolutePath() + "/jettyhome"; - bootBundleCanBeJarred = false; - } - } - // in case we stripped the quotes. - System.setProperty("jetty.home",jettyHome); - - String jettyLogs = stripQuotesIfPresent(System.getProperty("jetty.logs")); - if (jettyLogs == null || jettyLogs.length() == 0) - { - System.setProperty("jetty.logs",jettyHome + "/logs"); - } - - if (!bootBundleCanBeJarred && !_installLocation.isDirectory()) - { - String install = _installLocation != null?_installLocation.getCanonicalPath():" unresolved_install_location"; - throw new IllegalArgumentException("The system property -Djetty.home" + " must be set to a directory or the bundle " - + context.getBundle().getSymbolicName() + " installed here " + install + " must be unjarred."); - - } - try - { - System.err.println("JETTY_HOME set to " + new File(jettyHome).getCanonicalPath()); - } - catch (Throwable t) - { - System.err.println("JETTY_HOME _set to " + new File(jettyHome).getAbsolutePath()); - } - - ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); - try - { - - // passing this bundle's classloader as the context classlaoder - // makes sure there is access to all the jetty's bundles - - File jettyHomeF = new File(jettyHome); - URLClassLoader libExtClassLoader = null; - try - { - libExtClassLoader = LibExtClassLoaderHelper.createLibEtcClassLoaderHelper(jettyHomeF,_server, - JettyBootstrapActivator.class.getClassLoader()); - } - catch (MalformedURLException e) - { - e.printStackTrace(); - } - - Thread.currentThread().setContextClassLoader(libExtClassLoader); - - String jettyetc = System.getProperty(OSGiWebappConstants.SYS_PROP_JETTY_ETC_FILES,"etc/jetty.xml"); - StringTokenizer tokenizer = new StringTokenizer(jettyetc,";,"); - - Map<Object,Object> id_map = new HashMap<Object,Object>(); - id_map.put("Server",_server); - Map<Object,Object> properties = new HashMap<Object,Object>(); - properties.put("jetty.home",jettyHome); - properties.put("jetty.host",System.getProperty("jetty.host","")); - properties.put("jetty.port",System.getProperty("jetty.port","8080")); - properties.put("jetty.port.ssl",System.getProperty("jetty.port.ssl","8443")); - - while (tokenizer.hasMoreTokens()) - { - String etcFile = tokenizer.nextToken().trim(); - File conffile = etcFile.startsWith("/")?new File(etcFile):new File(jettyHomeF,etcFile); - if (!conffile.exists()) - { - __logger.warn("Unable to resolve the jetty/etc file " + etcFile); - - if ("etc/jetty.xml".equals(etcFile)) - { - // Missing jetty.xml file, so create a minimal Jetty configuration - __logger.info("Configuring default server on 8080"); - SelectChannelConnector connector = new SelectChannelConnector(); - connector.setPort(8080); - _server.addConnector(connector); - - HandlerCollection handlers = new HandlerCollection(); - ContextHandlerCollection contexts = new ContextHandlerCollection(); - RequestLogHandler requestLogHandler = new RequestLogHandler(); - handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(), requestLogHandler }); - _server.setHandler(handlers); - } - } - else - { - try - { - // Execute a Jetty configuration file - XmlConfiguration config = new XmlConfiguration(new FileInputStream(conffile)); - config.setIdMap(id_map); - config.setProperties(properties); - config.configure(); - id_map=config.getIdMap(); - } - catch (SAXParseException saxparse) - { - Log.getLogger(WebappRegistrationHelper.class.getName()).warn("Unable to configure the jetty/etc file " + etcFile,saxparse); - throw saxparse; - } - } - } - - init(); - - //now that we have an app provider we can call the registration customizer. - try - { - URL[] jarsWithTlds = getJarsWithTlds(); - _commonParentClassLoaderForWebapps = jarsWithTlds == null?libExtClassLoader:new TldLocatableURLClassloader(libExtClassLoader,getJarsWithTlds()); - } - catch (MalformedURLException e) - { - e.printStackTrace(); - } - - - _server.start(); - } - catch (Throwable t) - { - t.printStackTrace(); - } - finally - { - Thread.currentThread().setContextClassLoader(contextCl); - } - - } - - /** - * Must be called after the server is configured. - * - * Locate the actual instance of the ContextDeployer and WebAppDeployer that - * was created when configuring the server through jetty.xml. If there is no - * such thing it won't be possible to deploy webapps from a context and we - * throw IllegalStateExceptions. - */ - private void init() - { - // Get the context handler - _ctxtHandler = (ContextHandlerCollection)_server.getChildHandlerByClass(ContextHandlerCollection.class); - - // get a deployerManager - List<DeploymentManager> deployers = _server.getBeans(DeploymentManager.class); - if (deployers != null && !deployers.isEmpty()) - { - _deploymentManager = deployers.get(0); - - for (AppProvider provider : _deploymentManager.getAppProviders()) - { - if (provider instanceof OSGiAppProvider) - { - _provider=(OSGiAppProvider)provider; - break; - } - } - if (_provider == null) - { - //create it on the fly with reasonable default values. - try - { - _provider = new OSGiAppProvider(); - _provider.setMonitoredDir( - Resource.newResource(getDefaultOSGiContextsHome( - new File(System.getProperty("jetty.home"))).toURI())); - } catch (IOException e) { - e.printStackTrace(); - } - _deploymentManager.addAppProvider(_provider); - } - } - - if (_ctxtHandler == null || _provider==null) - throw new IllegalStateException("ERROR: No ContextHandlerCollection or OSGiAppProvider configured"); - - - } - - /** - * Deploy a new web application on the jetty server. - * - * @param context - * The current bundle context - * @param webappFolderPath - * The path to the root of the webapp. Must be a path relative to - * bundle; either an absolute path. - * @param contextPath - * The context path. Must start with "/" - * @param classInBundle - * A class that belongs to the current bundle to inherit from the - * osgi classloader. Null to not have access to the OSGI - * classloader. - * @throws Exception - */ - public ContextHandler registerWebapplication(Bundle bundle, String webappFolderPath, String contextPath, String extraClasspath, - String overrideBundleInstallLocation, String webXmlPath, String defaultWebXmlPath) throws Exception - { - File bundleInstall = overrideBundleInstallLocation == null?BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(bundle):new File( - overrideBundleInstallLocation); - File webapp = null; - if (webappFolderPath != null && webappFolderPath.length() != 0 && !webappFolderPath.equals(".")) - { - if (webappFolderPath.startsWith("/") || webappFolderPath.startsWith("file:/")) - { - webapp = new File(webappFolderPath); - } - else - { - webapp = new File(bundleInstall,webappFolderPath); - } - } - else - { - webapp = bundleInstall; - } - if (!webapp.exists()) - { - throw new IllegalArgumentException("Unable to locate " + webappFolderPath + " inside " - + (bundleInstall != null?bundleInstall.getAbsolutePath():"unlocated bundle '" + bundle.getSymbolicName() + "'")); - } - return registerWebapplication(bundle,webapp,contextPath,extraClasspath,bundleInstall,webXmlPath,defaultWebXmlPath); - } - - /** - * @See {@link WebAppDeployer#scan()} - * TODO: refacotr this into the createContext method of OSGiAppProvider. - * - * @param webapp - * @param contextPath - * @param classInBundle - * @return The contexthandler created and started - * @throws Exception - */ - public ContextHandler registerWebapplication(Bundle contributor, File webapp, String contextPath, String extraClasspath, File bundleInstall, - String webXmlPath, String defaultWebXmlPath) throws Exception - { - - ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); - String[] oldServerClasses = null; - WebAppContext context = null; - try - { - // make sure we provide access to all the jetty bundles by going - // through this bundle. - OSGiWebappClassLoader composite = createWebappClassLoader(contributor); - // configure with access to all jetty classes and also all the classes - // that the contributor gives access to. - Thread.currentThread().setContextClassLoader(composite); - - context = new WebAppContext(webapp.getAbsolutePath(),contextPath); - context.setExtraClasspath(extraClasspath); - - if (webXmlPath != null && webXmlPath.length() != 0) - { - File webXml = null; - if (webXmlPath.startsWith("/") || webXmlPath.startsWith("file:/")) - { - webXml = new File(webXmlPath); - } - else - { - webXml = new File(bundleInstall,webXmlPath); - } - if (webXml.exists()) - { - context.setDescriptor(webXml.getAbsolutePath()); - } - } - - if (defaultWebXmlPath == null || defaultWebXmlPath.length() == 0) - { - //use the one defined by the OSGiAppProvider. - defaultWebXmlPath = _provider.getDefaultsDescriptor(); - } - if (defaultWebXmlPath != null && defaultWebXmlPath.length() != 0) - { - File defaultWebXml = null; - if (defaultWebXmlPath.startsWith("/") || defaultWebXmlPath.startsWith("file:/")) - { - defaultWebXml = new File(webXmlPath); - } - else - { - defaultWebXml = new File(bundleInstall,defaultWebXmlPath); - } - if (defaultWebXml.exists()) - { - context.setDefaultsDescriptor(defaultWebXml.getAbsolutePath()); - } - } - - //other parameters that might be defines on the OSGiAppProvider: - context.setParentLoaderPriority(_provider.isParentLoaderPriority()); - - configureWebAppContext(context,contributor); - configureWebappClassLoader(contributor,context,composite); - - // @see - // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) - // during initialization of the webapp all the jetty packages are - // visible - // through the webapp classloader. - oldServerClasses = context.getServerClasses(); - context.setServerClasses(null); - _provider.addContext(context); - - return context; - } - finally - { - if (context != null && oldServerClasses != null) - { - context.setServerClasses(oldServerClasses); - } - Thread.currentThread().setContextClassLoader(contextCl); - } - - } - - /** - * Stop a ContextHandler and remove it from the collection. - * - * @See ContextDeployer#undeploy - * @param contextHandler - * @throws Exception - */ - public void unregister(ContextHandler contextHandler) throws Exception - { - contextHandler.stop(); - _ctxtHandler.removeHandler(contextHandler); - } - - /** - * @return The default folder in which the context files of the osgi bundles - * are located and watched. Or null when the system property - * "jetty.osgi.contexts.home" is not defined. - * If the configuration file defines the OSGiAppProvider's context. - * This will not be taken into account. - */ - File getDefaultOSGiContextsHome(File jettyHome) - { - String jettyContextsHome = System.getProperty("jetty.osgi.contexts.home"); - if (jettyContextsHome != null) - { - File contextsHome = new File(jettyContextsHome); - if (!contextsHome.exists() || !contextsHome.isDirectory()) - { - throw new IllegalArgumentException("the ${jetty.osgi.contexts.home} '" + jettyContextsHome + " must exist and be a folder"); - } - return contextsHome; - } - return new File(jettyHome, "/contexts"); - } - - File getOSGiContextsHome() - { - return _provider.getContextXmlDirAsFile(); - } - - /** - * This type of registration relies on jetty's complete context xml file. - * Context encompasses jndi and all other things. This makes the definition - * of the webapp a lot more self-contained. - * - * @param webapp - * @param contextPath - * @param classInBundle - * @throws Exception - */ - public ContextHandler registerContext(Bundle contributor, String contextFileRelativePath, String extraClasspath, String overrideBundleInstallLocation) - throws Exception - { - File contextsHome = _provider.getContextXmlDirAsFile(); - if (contextsHome != null) - { - File prodContextFile = new File(contextsHome,contributor.getSymbolicName() + "/" + contextFileRelativePath); - if (prodContextFile.exists()) - { - return registerContext(contributor,prodContextFile,extraClasspath,overrideBundleInstallLocation); - } - } - File contextFile = overrideBundleInstallLocation != null?new File(overrideBundleInstallLocation,contextFileRelativePath):new File( - BUNDLE_FILE_LOCATOR_HELPER.getBundleInstallLocation(contributor),contextFileRelativePath); - if (contextFile.exists()) - { - return registerContext(contributor,contextFile,extraClasspath,overrideBundleInstallLocation); - } - else - { - if (contextFileRelativePath.startsWith("./")) - { - contextFileRelativePath = contextFileRelativePath.substring(1); - } - if (!contextFileRelativePath.startsWith("/")) - { - contextFileRelativePath = "/" + contextFileRelativePath; - } - if (overrideBundleInstallLocation == null) - { - URL contextURL = contributor.getEntry(contextFileRelativePath); - if (contextURL != null) - { - return registerContext(contributor,contextURL.openStream(),extraClasspath,overrideBundleInstallLocation); - } - } - else - { - JarFile zipFile = null; - try - { - zipFile = new JarFile(overrideBundleInstallLocation); - ZipEntry entry = zipFile.getEntry(contextFileRelativePath.substring(1)); - return registerContext(contributor,zipFile.getInputStream(entry),extraClasspath,overrideBundleInstallLocation); - } - catch (Throwable t) - { - - } - finally - { - if (zipFile != null) - try - { - zipFile.close(); - } - catch (IOException ioe) - { - } - } - } - throw new IllegalArgumentException("Could not find the context " + "file " + contextFileRelativePath + " for the bundle " - + contributor.getSymbolicName() + (overrideBundleInstallLocation != null?" using the install location " + overrideBundleInstallLocation:"")); - } - } - - /** - * This type of registration relies on jetty's complete context xml file. - * Context encompasses jndi and all other things. This makes the definition - * of the webapp a lot more self-contained. - * - * @param webapp - * @param contextPath - * @param classInBundle - * @throws Exception - */ - private ContextHandler registerContext(Bundle contributor, File contextFile, String extraClasspath, String overrideBundleInstallLocation) throws Exception - { - InputStream contextFileInputStream = null; - try - { - contextFileInputStream = new BufferedInputStream(new FileInputStream(contextFile)); - return registerContext(contributor,contextFileInputStream,extraClasspath,overrideBundleInstallLocation); - } - finally - { - if (contextFileInputStream != null) - try - { - contextFileInputStream.close(); - } - catch (IOException ioe) - { - } - } - } - - /** - * @param contributor - * @param contextFileInputStream - * @return The ContextHandler created and registered or null if it did not - * happen. - * @throws Exception - */ - private ContextHandler registerContext(Bundle contributor, InputStream contextFileInputStream, String extraClasspath, String overrideBundleInstallLocation) - throws Exception - { - ClassLoader contextCl = Thread.currentThread().getContextClassLoader(); - String[] oldServerClasses = null; - WebAppContext webAppContext = null; - try - { - // make sure we provide access to all the jetty bundles by going - // through this bundle. - OSGiWebappClassLoader composite = createWebappClassLoader(contributor); - // configure with access to all jetty classes and also all the - // classes - // that the contributor gives access to. - Thread.currentThread().setContextClassLoader(composite); - ContextHandler context = createContextHandler(contributor,contextFileInputStream,extraClasspath,overrideBundleInstallLocation); - if (context == null) - { - return null;// did not happen - } - - // ok now register this webapp. we checked when we started jetty - // that there - // was at least one such handler for webapps. - //the actual registration must happen via the new Deployment API. -// _ctxtHandler.addHandler(context); - - configureWebappClassLoader(contributor,context,composite); - if (context instanceof WebAppContext) - { - webAppContext = (WebAppContext)context; - // @see - // org.eclipse.jetty.webapp.JettyWebXmlConfiguration#configure(WebAppContext) - oldServerClasses = webAppContext.getServerClasses(); - webAppContext.setServerClasses(null); - } - - context.start(); - return context; - } - finally - { - if (webAppContext != null) - { - webAppContext.setServerClasses(oldServerClasses); - } - Thread.currentThread().setContextClassLoader(contextCl); - } - - } - - /** - * TODO: right now only the jetty-jsp bundle is scanned for common taglibs. - * Should support a way to plug more bundles that contain taglibs. - * - * The jasper TldScanner expects a URLClassloader to parse a jar for the - * /META-INF/*.tld it may contain. We place the bundles that we know contain - * such tag-libraries. Please note that it will work if and only if the - * bundle is a jar (!) Currently we just hardcode the bundle that contains - * the jstl implemenation. - * - * A workaround when the tld cannot be parsed with this method is to copy - * and paste it inside the WEB-INF of the webapplication where it is used. - * - * Support only 2 types of packaging for the bundle: - the bundle is a jar - * (recommended for runtime.) - the bundle is a folder and contain jars in - * the root and/or in the lib folder (nice for PDE developement situations) - * Unsupported: the bundle is a jar that embeds more jars. - * - * @return - * @throws Exception - */ - private URL[] getJarsWithTlds() throws Exception - { - ArrayList<URL> res = new ArrayList<URL>(); - for (WebappRegistrationCustomizer regCustomizer : JSP_REGISTRATION_HELPERS) - { - URL[] urls = regCustomizer.getJarsWithTlds(_provider, BUNDLE_FILE_LOCATOR_HELPER); - for (URL url : urls) - { - if (!res.contains(url)) - res.add(url); - } - } - if (!res.isEmpty()) - return res.toArray(new URL[res.size()]); - else - return null; - } - - /** - * Applies the properties of WebAppDeployer as defined in jetty.xml. - * - * @see {WebAppDeployer#scan} around the comment - * <code>// configure it</code> - */ - protected void configureWebAppContext(WebAppContext wah, Bundle contributor) - { - // rfc66 - wah.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,contributor.getBundleContext()); - - //spring-dm-1.2.1 looks for the BundleContext as a different attribute. - //not a spec... but if we want to support - //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext - //then we need to do this to: - wah.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), - contributor.getBundleContext()); - - } - - /** - * @See {@link ContextDeployer#scan} - * @param contextFile - * @return - */ - protected ContextHandler createContextHandler(Bundle bundle, File contextFile, String extraClasspath, String overrideBundleInstallLocation) - { - try - { - return createContextHandler(bundle,new BufferedInputStream(new FileInputStream(contextFile)),extraClasspath,overrideBundleInstallLocation); - } - catch (FileNotFoundException e) - { - e.printStackTrace(); - } - return null; - } - - /** - * @See {@link ContextDeployer#scan} - * @param contextFile - * @return - */ - @SuppressWarnings("unchecked") - protected ContextHandler createContextHandler(Bundle bundle, InputStream contextInputStream, String extraClasspath, String overrideBundleInstallLocation) - { - /* - * Do something identical to what the ContextProvider would have done: - * XmlConfiguration xmlConfiguration=new - * XmlConfiguration(resource.getURL()); HashMap properties = new - * HashMap(); properties.put("Server", _contexts.getServer()); if - * (_configMgr!=null) properties.putAll(_configMgr.getProperties()); - * - * xmlConfiguration.setProperties(properties); ContextHandler - * context=(ContextHandler)xmlConfiguration.configure(); - * context.setAttributes(new AttributesMap(_contextAttributes)); - */ - try - { - XmlConfiguration xmlConfiguration = new XmlConfiguration(contextInputStream); - HashMap properties = new HashMap(); - properties.put("Server",_server); - - // insert the bundle's location as a property. - setThisBundleHomeProperty(bundle,properties,overrideBundleInstallLocation); - xmlConfiguration.setProperties(properties); - - ContextHandler context = (ContextHandler)xmlConfiguration.configure(); - if (context instanceof WebAppContext) - { - ((WebAppContext)context).setExtraClasspath(extraClasspath); - ((WebAppContext)context).setParentLoaderPriority(_provider.isParentLoaderPriority()); - if (_provider.getDefaultsDescriptor() != null && _provider.getDefaultsDescriptor().length() != 0) - { - ((WebAppContext)context).setDefaultsDescriptor(_provider.getDefaultsDescriptor()); - } - } - - // rfc-66: - context.setAttribute(OSGiWebappConstants.RFC66_OSGI_BUNDLE_CONTEXT,bundle.getBundleContext()); - - //spring-dm-1.2.1 looks for the BundleContext as a different attribute. - //not a spec... but if we want to support - //org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext - //then we need to do this to: - context.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), - bundle.getBundleContext()); - return context; - } - catch (FileNotFoundException e) - { - return null; - } - catch (SAXException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - catch (IOException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - catch (Throwable e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - finally - { - if (contextInputStream != null) - try - { - contextInputStream.close(); - } - catch (IOException ioe) - { - } - } - return null; - } - - /** - * Configure a classloader onto the context. If the context is a - * WebAppContext, build a WebAppClassLoader that has access to all the jetty - * classes thanks to the classloader of the JettyBootStrapper bundle and - * also has access to the classloader of the bundle that defines this - * context. - * <p> - * If the context is not a WebAppContext, same but with a simpler - * URLClassLoader. Note that the URLClassLoader is pretty much fake: it - * delegate all actual classloading to the parent classloaders. - * </p> - * <p> - * The URL[] returned by the URLClassLoader create contained specifically - * the jars that some j2ee tools expect and look into. For example the jars - * that contain tld files for jasper's jstl support. - * </p> - * <p> - * Also as the jars in the lib folder and the classes in the classes folder - * might already be in the OSGi classloader we filter them out of the - * WebAppClassLoader - * </p> - * - * @param context - * @param contributor - * @param webapp - * @param contextPath - * @param classInBundle - * @throws Exception - */ - protected void configureWebappClassLoader(Bundle contributor, ContextHandler context, OSGiWebappClassLoader webappClassLoader) throws Exception - { - if (context instanceof WebAppContext) - { - WebAppContext webappCtxt = (WebAppContext)context; - context.setClassLoader(webappClassLoader); - webappClassLoader.setWebappContext(webappCtxt); - } - else - { - context.setClassLoader(webappClassLoader); - } - } - - /** - * No matter what the type of webapp, we create a WebappClassLoader. - */ - protected OSGiWebappClassLoader createWebappClassLoader(Bundle contributor) throws Exception - { - // we use a temporary WebAppContext object. - // if this is a real webapp we will set it on it a bit later: once we - // know. - OSGiWebappClassLoader webappClassLoader = new OSGiWebappClassLoader(_commonParentClassLoaderForWebapps,new WebAppContext(),contributor); - return webappClassLoader; - } - - /** - * Set the property "this.bundle.install" to point to the location - * of the bundle. Useful when <SystemProperty name="this.bundle.home"/> is - * used. - */ - private void setThisBundleHomeProperty(Bundle bundle, HashMap<String, Object> properties, String overrideBundleInstallLocation) - { - try - { - File location = overrideBundleInstallLocation != null?new File(overrideBundleInstallLocation):BUNDLE_FILE_LOCATOR_HELPER - .getBundleInstallLocation(bundle); - properties.put("this.bundle.install",location.getCanonicalPath()); - } - catch (Throwable t) - { - System.err.println("Unable to set 'this.bundle.install' " + " for the bundle " + bundle.getSymbolicName()); - t.printStackTrace(); - } - } - - -} diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java index 8aef238472..820a627e1f 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/BundleFileLocatorHelper.java @@ -13,6 +13,8 @@ package org.eclipse.jetty.osgi.boot.utils; import java.io.File; +import java.net.URL; +import java.util.Enumeration; import org.eclipse.jetty.osgi.boot.utils.internal.DefaultFileLocatorHelper; import org.osgi.framework.Bundle; @@ -53,7 +55,7 @@ public interface BundleFileLocatorHelper * * @param bundle * @param path - * @return + * @return file object * @throws Exception */ public File getFileInBundle(Bundle bundle, String path) throws Exception; @@ -73,5 +75,16 @@ public interface BundleFileLocatorHelper * embedded inside it. */ public File[] locateJarsInsideBundle(Bundle bundle) throws Exception; + + + /** + * Helper method equivalent to Bundle#getEntry(String entryPath) except that + * it searches for entries in the fragments by using the findEntries method. + * + * @param bundle + * @param entryPath + * @return null or all the entries found for that path. + */ + public Enumeration<URL> findEntries(Bundle bundle, String entryPath); } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java index 5db0a2f4de..1a37a046c6 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/WebappRegistrationCustomizer.java @@ -46,7 +46,7 @@ public interface WebappRegistrationCustomizer * the root and/or in the lib folder (nice for PDE developement situations) * Unsupported: the bundle is a jar that embeds more jars. * - * @return + * @return array of URLs * @throws Exception */ URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper fileLocator) throws Exception; diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java index a3541b60ac..a918cd7706 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultBundleClassLoaderHelper.java @@ -14,6 +14,8 @@ package org.eclipse.jetty.osgi.boot.utils.internal; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLConnection; import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelper; import org.osgi.framework.Bundle; @@ -59,7 +61,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper * Assuming the bundle is started. * * @param bundle - * @return + * @return classloader object */ public ClassLoader getBundleClassLoader(Bundle bundle) { @@ -180,4 +182,5 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper } return null; } + } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java index 5dfc3676df..ec8f997d69 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/utils/internal/DefaultFileLocatorHelper.java @@ -14,10 +14,13 @@ package org.eclipse.jetty.osgi.boot.utils.internal; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Enumeration; import java.util.zip.ZipFile; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; @@ -66,8 +69,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper // grab the MANIFEST.MF's url // and then do what it takes. URL url = bundle.getEntry("/META-INF/MANIFEST.MF"); - // System.err.println(url.toString() + " " + url.toURI() + " " + - // url.getProtocol()); +// System.err.println(url.toString() + " " + url.toURI() + " " + url.getProtocol()); if (url.getProtocol().equals("file")) { // some osgi frameworks do use the file protocole directly in some @@ -130,11 +132,36 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper { // observed this on felix-2.0.0 String location = bundle.getLocation(); +// System.err.println("location " + location); if (location.startsWith("file:/")) { URI uri = new URI(URIUtil.encodePath(location)); return new File(uri); } + else if (location.startsWith("file:")) + { + //location defined in the BundleArchive m_bundleArchive + //it is relative to relative to the BundleArchive's m_archiveRootDir + File res = new File(location.substring("file:".length())); + if (!res.exists()) + { + return null; +// Object bundleArchive = getFelixBundleArchive(bundle); +// File archiveRoot = getFelixBundleArchiveRootDir(bundleArchive); +// String currentLocation = getFelixBundleArchiveCurrentLocation(bundleArchive); +// System.err.println("Got the archive root " + archiveRoot.getAbsolutePath() +// + " current location " + currentLocation + " is directory ?"); +// res = new File(archiveRoot, currentLocation != null +// ? currentLocation : location.substring("file:".length())); + } + return res; + } + else if (location.startsWith("reference:file:")) + { + location = URLDecoder.decode(location.substring("reference:".length()), "UTF-8"); + File file = new File(location.substring("file:".length())); + return file; + } } return null; } @@ -144,7 +171,7 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper * * @param bundle * @param path - * @return + * @return file object * @throws Exception */ public File getFileInBundle(Bundle bundle, String path) throws Exception @@ -162,6 +189,29 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper } return webapp; } + + /** + * Helper method equivalent to Bundle#getEntry(String entryPath) except that + * it searches for entries in the fragments by using the Bundle#findEntries method. + * + * @param bundle + * @param entryPath + * @return null or all the entries found for that path. + */ + public Enumeration<URL> findEntries(Bundle bundle, String entryPath) + { + int last = entryPath.lastIndexOf('/'); + String path = last != -1 && last < entryPath.length() -2 + ? entryPath.substring(0, last) : "/"; + if (!path.startsWith("/")) + { + path = "/" + path; + } + String pattern = last != -1 && last < entryPath.length() -2 + ? entryPath.substring(last+1) : entryPath; + Enumeration<URL> enUrls = bundle.findEntries(path, pattern, false); + return enUrls; + } /** * If the bundle is a jar, returns the jar. If the bundle is a folder, look @@ -205,9 +255,78 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper } else { - return new File[] - { jasperLocation }; + return new File[] { jasperLocation }; } } + + + //introspection on equinox to invoke the getLocalURL method on BundleURLConnection + //equivalent to using the FileLocator without depending on an equinox class. + private static Method BUNDLE_URL_CONNECTION_getLocalURL = null; + private static Method BUNDLE_URL_CONNECTION_getFileURL = null; + /** + * Only useful for equinox: on felix we get the file:// or jar:// url already. + * Other OSGi implementations have not been tested + * <p> + * Get a URL to the bundle entry that uses a common protocol (i.e. file: + * jar: or http: etc.). + * </p> + * @return a URL to the bundle entry that uses a common protocol + */ + public static URL getLocalURL(URL url) { + if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) { + try { + URLConnection conn = url.openConnection(); + if (BUNDLE_URL_CONNECTION_getLocalURL == null && + conn.getClass().getName().equals( + "org.eclipse.osgi.framework.internal.core.BundleURLConnection")) { + BUNDLE_URL_CONNECTION_getLocalURL = conn.getClass().getMethod("getLocalURL", null); + BUNDLE_URL_CONNECTION_getLocalURL.setAccessible(true); + } + if (BUNDLE_URL_CONNECTION_getLocalURL != null) { + return (URL)BUNDLE_URL_CONNECTION_getLocalURL.invoke(conn, null); + } + } catch (Throwable t) { + t.printStackTrace(); + } + } + return url; + } + /** + * Only useful for equinox: on felix we get the file:// url already. + * Other OSGi implementations have not been tested + * <p> + * Get a URL to the content of the bundle entry that uses the file: protocol. + * The content of the bundle entry may be downloaded or extracted to the local + * file system in order to create a file: URL. + * @return a URL to the content of the bundle entry that uses the file: protocol + * </p> + */ + public static URL getFileURL(URL url) + { + if ("bundleresource".equals(url.getProtocol()) || "bundleentry".equals(url.getProtocol())) + { + try + { + URLConnection conn = url.openConnection(); + if (BUNDLE_URL_CONNECTION_getFileURL == null && + conn.getClass().getName().equals( + "org.eclipse.osgi.framework.internal.core.BundleURLConnection")) + { + BUNDLE_URL_CONNECTION_getFileURL = conn.getClass().getMethod("getFileURL", null); + BUNDLE_URL_CONNECTION_getFileURL.setAccessible(true); + } + if (BUNDLE_URL_CONNECTION_getFileURL != null) + { + return (URL)BUNDLE_URL_CONNECTION_getFileURL.invoke(conn, null); + } + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + return url; + } } |