From 5e2110d89429bf7ff557fb09e9f0da0b4c3783a4 Mon Sep 17 00:00:00 2001 From: Hugues Malphettes Date: Sun, 20 Jun 2010 01:39:24 +0000 Subject: [PATCH] in progress: support for multiple instances of jetty git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2029 7e9141cc-0065-0410-87d8-b60c137991c4 --- .../osgi/boot/JettyBootstrapActivator.java | 3 +- .../jetty/osgi/boot/OSGiWebappConstants.java | 2 +- .../JettyServersManagedFactory.java | 18 +- .../serverfactory/ServerInstanceWrapper.java | 374 ++++++++++++++++++ .../webapp/LibExtClassLoaderHelper.java | 48 +++ .../webapp/WebappRegistrationHelper.java | 2 - 6 files changed, 433 insertions(+), 14 deletions(-) create mode 100644 jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java 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 66a3f7506e2..ed9b073e5ca 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 @@ -30,7 +30,6 @@ 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.Constants; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.Configuration; @@ -321,7 +320,7 @@ public class JettyBootstrapActivator implements BundleActivator } - properties.put(OSGiWebappConstants.MANAGED_JETTY_JETTY_XML_URL, actualBundleUrls.toString()); + properties.put(OSGiWebappConstants.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/OSGiWebappConstants.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebappConstants.java index 34c0e434163..08bf9ad2de3 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 @@ -98,6 +98,6 @@ public class OSGiWebappConstants /** * 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_URLS = "managedJettySharedLibUrls"; + 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/internal/serverfactory/JettyServersManagedFactory.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/JettyServersManagedFactory.java index be759042411..1ac477ce2e5 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 @@ -83,7 +83,7 @@ public class JettyServersManagedFactory implements ManagedServiceFactory /** * Servers indexed by PIDs. PIDs are generated by the ConfigurationAdmin service. */ - private Map _serversIndexedByPID = new HashMap(); + private Map _serversIndexedByPID = new HashMap(); /** * PID -> {@link OSGiWebappConstants#MANAGED_JETTY_SERVER_NAME} */ @@ -105,29 +105,29 @@ public class JettyServersManagedFactory implements ManagedServiceFactory public void updated(String pid, Dictionary properties) throws ConfigurationException { - Server server = getServerByPID(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. - server = new Server(); - _serversIndexedByPID.put(pid, server); String name = (String)properties.get(OSGiWebappConstants.MANAGED_JETTY_SERVER_NAME); if (name == null) { throw new ConfigurationException(OSGiWebappConstants.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); - ServerInstanceWrapperHelper.start(server, pid, properties); + serverInstanceWrapper.start(new Server(), properties); } public synchronized void deleted(String pid) { - Server server = (Server)_serversIndexedByPID.remove(pid); + ServerInstanceWrapper server = (ServerInstanceWrapper)_serversIndexedByPID.remove(pid); String name = _serversNameIndexedByPID.remove(pid); if (name != null) { @@ -141,7 +141,7 @@ public class JettyServersManagedFactory implements ManagedServiceFactory { try { - ServerInstanceWrapperHelper.stop(server, pid); + server.stop(); } catch (Exception e) { @@ -150,12 +150,12 @@ public class JettyServersManagedFactory implements ManagedServiceFactory } } - public synchronized Server getServerByPID(String pid) + public synchronized ServerInstanceWrapper getServerByPID(String pid) { return _serversIndexedByPID.get(pid); } - public synchronized Server getServerByName(String name) + public synchronized ServerInstanceWrapper getServerByName(String name) { String pid = _serversPIDIndexedByName.get(name); return pid != null ? _serversIndexedByPID.get(pid) : null; 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 00000000000..5a70b8f7954 --- /dev/null +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java @@ -0,0 +1,374 @@ +// ======================================================================== +// 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.FileInputStream; +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.Collections; +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.OSGiWebappConstants; +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.WebappRegistrationHelper; +import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; +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.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.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 outside of 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; + + + public ServerInstanceWrapper(String managedServerName) + { + _managedServerName = managedServerName; + } + + + 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 + URLClassLoader libExtClassLoader = null; + String sharedURLs = (String)props.get(OSGiWebappConstants.MANAGED_JETTY_SHARED_LIB_FOLDER_URLS); + try + { + List 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); + } + + } + + + public void stop() + { + try { + _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 res = new ArrayList(); + for (WebappRegistrationCustomizer regCustomizer : WebappRegistrationHelper.JSP_REGISTRATION_HELPERS) + { + URL[] urls = regCustomizer.getJarsWithTlds(_provider, WebappRegistrationHelper.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(OSGiWebappConstants.MANAGED_JETTY_XML_CONFIG_URLS); + List jettyConfigurations = jettyConfigurationUrls != null + ? extractResources(jettyConfigurationUrls) : null; + if (jettyConfigurations == null || jettyConfigurations.isEmpty()) + { + return; + } + Map id_map = new HashMap(); + id_map.put("Server",server); + Map properties = new HashMap(); + 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) + { + Log.getLogger(WebappRegistrationHelper.class.getName()) + .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 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 extractResources(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List urls = new ArrayList(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + urls.add(((DefaultFileLocatorHelper) WebappRegistrationHelper + .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 extractFiles(String propertyValue) + { + StringTokenizer tokenizer = new StringTokenizer(propertyValue, ",;", false); + List files = new ArrayList(); + while (tokenizer.hasMoreTokens()) + { + String tok = tokenizer.nextToken(); + try + { + URL url = new URL(tok); + url = ((DefaultFileLocatorHelper) WebappRegistrationHelper + .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/LibExtClassLoaderHelper.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/webapp/LibExtClassLoaderHelper.java index e93585277d4..d11e0bbe4dd 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; @@ -92,6 +93,18 @@ public class LibExtClassLoaderHelper * @throws MalformedURLException */ public static URLClassLoader createLibEtcClassLoaderHelper(File jettyHome, Server server, ClassLoader parentClassLoader) throws MalformedURLException + { + return null; + } + /** + * @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. + * @throws MalformedURLException + */ + public static URLClassLoader createLibEtcClassLoader(File jettyHome, Server server, + ClassLoader parentClassLoader) throws MalformedURLException { ArrayList urls = new ArrayList(); @@ -138,6 +151,41 @@ public class LibExtClassLoaderHelper return new URLClassLoader(urls.toArray(new URL[urls.size()]),parentClassLoader); } + /** + * @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. + * @throws MalformedURLException + */ + public static URLClassLoader createLibExtClassLoader(List jarsContainerOrJars, + List otherJarsOrFolder, Server server, + ClassLoader parentClassLoader) throws MalformedURLException + { + List urls = new ArrayList(otherJarsOrFolder); + 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 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 index b4d0db83715..cc83564c397 100644 --- 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 @@ -347,8 +347,6 @@ public class WebappRegistrationHelper { e.printStackTrace(); } - - _server.start(); } catch (Throwable t)