| // ======================================================================== |
| // 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 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 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); |
| } |
| |
| /** |
| * TODO: refactor this into the createContext method of OSGiAppProvider. |
| * @see WebAppDeployer#scan() |
| |
| * @param contributor |
| * @param webapp |
| * @param contextPath |
| * @param extraClasspath |
| * @param bundleInstall |
| * @param webXmlPath |
| * @param defaultWebXmlPath |
| * @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 contributor |
| * @param contextFileRelativePath |
| * @param extraClasspath |
| * @param overrideBundleInstallLocation |
| * @return The contexthandler created and started |
| * @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); |
| } |
| |
| _provider.addContext(context); |
| 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(); |
| } |
| } |
| |
| |
| } |