blob: a72453b242d682188445934248b9b65b585b449e [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
import org.eclipse.jetty.osgi.boot.internal.webapp.OSGiWebappClassLoader;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.JarResource;
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.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
/**
* AbstractWebAppProvider
*
* Base class for Jetty DeploymentManager Providers that are capable of deploying a webapp,
* either from a bundle or an OSGi service.
*
*/
public abstract class AbstractWebAppProvider extends AbstractLifeCycle implements AppProvider
{
private static final Logger LOG = Log.getLogger(AbstractWebAppProvider.class);
public static String __defaultConfigurations[] = {
"org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration",
"org.eclipse.jetty.webapp.WebXmlConfiguration",
"org.eclipse.jetty.webapp.MetaInfConfiguration",
"org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration"
};
public static void setDefaultConfigurations (String[] defaultConfigs)
{
__defaultConfigurations = defaultConfigs;
}
public static String[] getDefaultConfigurations ()
{
List<String> configs = ArrayUtil.asMutableList(__defaultConfigurations);
if (annotationsAvailable())
{
//add before JettyWebXmlConfiguration
int i = configs.indexOf("org.eclipse.jetty.webapp.JettyWebXmlConfiguration");
configs.add(i, "org.eclipse.jetty.osgi.annotations.AnnotationConfiguration");
}
if (jndiAvailable())
{
//add in EnvConfiguration and PlusConfiguration just after FragmentConfiguration
int i = configs.indexOf("org.eclipse.jetty.webapp.FragmentConfiguration");
configs.add(++i, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
configs.add(++i, "org.eclipse.jetty.plus.webapp.PlusConfiguration");
}
return configs.toArray(new String[configs.size()]);
}
private static boolean annotationsAvailable()
{
boolean result = false;
try
{
Loader.loadClass(AbstractWebAppProvider.class,"org.eclipse.jetty.annotations.AnnotationConfiguration");
result = true;
LOG.debug("Annotation support detected");
}
catch (ClassNotFoundException e)
{
result = false;
LOG.debug("No annotation support detected");
}
return result;
}
private static boolean jndiAvailable()
{
try
{
Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.jndi.Resource");
Loader.loadClass(AbstractWebAppProvider.class, "org.eclipse.jetty.plus.webapp.EnvConfiguration");
LOG.debug("JNDI support detected");
return true;
}
catch (ClassNotFoundException e)
{
LOG.debug("No JNDI support detected");
return false;
}
}
private boolean _parentLoaderPriority;
private String _defaultsDescriptor;
private boolean _extractWars = true; //See WebAppContext.extractWars
private String _tldBundles;
private DeploymentManager _deploymentManager;
private String[] _configurationClasses;
private ServerInstanceWrapper _serverWrapper;
/* ------------------------------------------------------------ */
/**
* OSGiApp
*
*
*/
public class OSGiApp extends AbstractOSGiApp
{
private String _contextPath;
private String _webAppPath;
private WebAppContext _webApp;
public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, String originId)
{
super(manager, provider, bundle, originId);
}
public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle, Dictionary properties, String originId)
{
super(manager, provider, bundle, properties, originId);
}
public void setWebAppContext (WebAppContext webApp)
{
_webApp = webApp;
}
public String getContextPath()
{
return _contextPath;
}
public void setContextPath(String contextPath)
{
this._contextPath = contextPath;
}
public String getBundlePath()
{
return _webAppPath;
}
public void setWebAppPath(String path)
{
this._webAppPath = path;
}
public ContextHandler createContextHandler()
throws Exception
{
if (_webApp != null)
{
configureWebApp();
return _webApp;
}
createWebApp();
return _webApp;
}
protected void createWebApp ()
throws Exception
{
_webApp = newWebApp();
configureWebApp();
}
protected WebAppContext newWebApp ()
{
WebAppContext webApp = new WebAppContext();
webApp.setAttribute(OSGiWebappConstants.WATERMARK, OSGiWebappConstants.WATERMARK);
//make sure we protect also the osgi dirs specified by OSGi Enterprise spec
String[] targets = webApp.getProtectedTargets();
String[] updatedTargets = null;
if (targets != null)
{
updatedTargets = new String[targets.length+OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length];
System.arraycopy(targets, 0, updatedTargets, 0, targets.length);
}
else
updatedTargets = new String[OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length];
System.arraycopy(OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS, 0, updatedTargets, targets.length, OSGiWebappConstants.DEFAULT_PROTECTED_OSGI_TARGETS.length);
webApp.setProtectedTargets(updatedTargets);
return webApp;
}
public void configureWebApp()
throws Exception
{
//TODO turn this around and let any context.xml file get applied first, and have the properties override
_webApp.setContextPath(_contextPath);
//osgi Enterprise Spec r4 p.427
_webApp.setAttribute(OSGiWebappConstants.OSGI_BUNDLECONTEXT, _bundle.getBundleContext());
String overrideBundleInstallLocation = (String)_properties.get(OSGiWebappConstants.JETTY_BUNDLE_INSTALL_LOCATION_OVERRIDE);
File bundleInstallLocation =
(overrideBundleInstallLocation == null
? BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(_bundle)
: new File(overrideBundleInstallLocation));
if (LOG.isDebugEnabled())
{
LOG.debug("Bundle location is {}, install location: {}", _bundle.getLocation(), bundleInstallLocation);
}
URL url = null;
Resource rootResource = Resource.newResource(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(bundleInstallLocation.toURI().toURL()));
//try and make sure the rootResource is useable - if its a jar then make it a jar file url
if (rootResource.exists()&& !rootResource.isDirectory() && !rootResource.toString().startsWith("jar:"))
{
Resource jarResource = JarResource.newJarResource(rootResource);
if (jarResource.exists() && jarResource.isDirectory())
rootResource = jarResource;
}
//if the path wasn't set or it was ., then it is the root of the bundle's installed location
if (_webAppPath == null || _webAppPath.length() == 0 || ".".equals(_webAppPath))
{
url = bundleInstallLocation.toURI().toURL();
if (LOG.isDebugEnabled())
LOG.debug("Webapp base using bundle install location: {}", url);
}
else
{
//Get the location of the root of the webapp inside the installed bundle
if (_webAppPath.startsWith("/") || _webAppPath.startsWith("file:"))
{
url = new File(_webAppPath).toURI().toURL();
if (LOG.isDebugEnabled())
LOG.debug("Webapp base using absolute location: {}", url);
}
else if (bundleInstallLocation != null && bundleInstallLocation.isDirectory())
{
url = new File(bundleInstallLocation, _webAppPath).toURI().toURL();
if (LOG.isDebugEnabled())
LOG.debug("Webapp base using path relative to bundle unpacked install location: {}", url);
}
else if (bundleInstallLocation != null)
{
Enumeration<URL> urls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(_bundle, _webAppPath);
if (urls != null && urls.hasMoreElements())
{
url = urls.nextElement();
if (LOG.isDebugEnabled())
LOG.debug("Webapp base using path relative to packed bundle location: {}", url);
}
}
}
if (url == null)
{
throw new IllegalArgumentException("Unable to locate " + _webAppPath
+ " in "
+ (bundleInstallLocation != null ? bundleInstallLocation.getAbsolutePath() : "unlocated bundle '" + _bundle.getSymbolicName()+ "'"));
}
//Sets the location of the war file
// converts bundleentry: protocol if necessary
_webApp.setWar(BundleFileLocatorHelperFactory.getFactory().getHelper().getLocalURL(url).toString());
// Set up what has been configured on the provider
_webApp.setParentLoaderPriority(isParentLoaderPriority());
_webApp.setExtractWAR(isExtract());
if (getConfigurationClasses() != null)
_webApp.setConfigurationClasses(getConfigurationClasses());
else
_webApp.setConfigurationClasses(getDefaultConfigurations());
if (getDefaultsDescriptor() != null)
_webApp.setDefaultsDescriptor(getDefaultsDescriptor());
//Set up configuration from manifest headers
//extra classpath
String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_EXTRA_CLASSPATH);
if (tmp != null)
_webApp.setExtraClasspath(tmp);
//web.xml
tmp = (String)_properties.get(OSGiWebappConstants.JETTY_WEB_XML_PATH);
if (tmp != null && tmp.trim().length() != 0)
{
File webXml = getFile (tmp, bundleInstallLocation);
if (webXml != null && webXml.exists())
_webApp.setDescriptor(webXml.getAbsolutePath());
}
//webdefault.xml
tmp = (String)_properties.get(OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH);
if (tmp != null)
{
File defaultWebXml = getFile (tmp, bundleInstallLocation);
if (defaultWebXml != null)
{
if (defaultWebXml.exists())
_webApp.setDefaultsDescriptor(defaultWebXml.getAbsolutePath());
else
LOG.warn(defaultWebXml.getAbsolutePath()+" does not exist");
}
}
//Handle Require-TldBundle
//This is a comma separated list of names of bundles that contain tlds that this webapp uses.
//We add them to the webapp classloader.
String requireTldBundles = (String)_properties.get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE);
String pathsToTldBundles = getPathsToRequiredBundles(requireTldBundles);
// make sure we provide access to all the jetty bundles by going
// through this bundle.
OSGiWebappClassLoader webAppLoader = new OSGiWebappClassLoader(_serverWrapper.getParentClassLoaderForWebapps(), _webApp, _bundle);
if (pathsToTldBundles != null)
webAppLoader.addClassPath(pathsToTldBundles);
_webApp.setClassLoader(webAppLoader);
// apply any META-INF/context.xml file that is found to configure
// the webapp first
applyMetaInfContextXml(rootResource, overrideBundleInstallLocation);
_webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles);
//Set up some attributes
// rfc66
_webApp.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:
_webApp.setAttribute("org.springframework.osgi.web." + BundleContext.class.getName(), _bundle.getBundleContext());
// also pass the bundle directly. sometimes a bundle does not have a
// bundlecontext.
// it is still useful to have access to the Bundle from the servlet
// context.
_webApp.setAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE, _bundle);
}
protected String getPathsToRequiredBundles (String requireTldBundles)
throws Exception
{
if (requireTldBundles == null) return null;
ServiceReference ref = _bundle.getBundleContext().getServiceReference(org.osgi.service.packageadmin.PackageAdmin.class.getName());
PackageAdmin packageAdmin = (ref == null) ? null : (PackageAdmin)_bundle.getBundleContext().getService(ref);
if (packageAdmin == null)
throw new IllegalStateException("Unable to get PackageAdmin reference to locate required Tld bundles");
StringBuilder paths = new StringBuilder();
String[] symbNames = requireTldBundles.split("[, ]");
for (String symbName : symbNames)
{
Bundle[] bs = packageAdmin.getBundles(symbName, null);
if (bs == null || bs.length == 0)
{
throw new IllegalArgumentException("Unable to locate the bundle '" + symbName
+ "' specified by "
+ OSGiWebappConstants.REQUIRE_TLD_BUNDLE
+ " in manifest of "
+ (_bundle == null ? "unknown" : _bundle.getSymbolicName()));
}
File f = BundleFileLocatorHelperFactory.getFactory().getHelper().getBundleInstallLocation(bs[0]);
if (paths.length() > 0) paths.append(", ");
paths.append(f.toURI().toURL().toString());
LOG.debug("getPathsToRequiredBundles: bundle path=" + bs[0].getLocation() + " uri=" + f.toURI());
}
return paths.toString();
}
protected void applyMetaInfContextXml(Resource rootResource, String overrideBundleInstallLocation)
throws Exception
{
if (_bundle == null) return;
if (_webApp == null) return;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
LOG.debug("Context classloader = " + cl);
try
{
Thread.currentThread().setContextClassLoader(_webApp.getClassLoader());
//TODO replace this with getting the InputStream so we don't cache in URL
//Try looking for a context xml file in META-INF with a specific name
URL contextXmlUrl = _bundle.getEntry("/META-INF/jetty-webapp-context.xml");
if (contextXmlUrl == null)
{
//Didn't find specially named file, try looking for a property that names a context xml file to use
if (_properties != null)
{
String tmp = (String)_properties.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
if (tmp != null)
{
String[] filenames = tmp.split("[,;]");
if (filenames != null && filenames.length > 0)
{
String filename = filenames[0]; //should only be 1 filename in this usage
String jettyHome = (String)getServerInstanceWrapper().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
if (jettyHome == null)
jettyHome = System.getProperty(OSGiServerConstants.JETTY_HOME);
Resource res = findFile(filename, jettyHome, overrideBundleInstallLocation, _bundle);
if (res != null)
contextXmlUrl = res.getURL();
}
}
}
}
if (contextXmlUrl == null) return;
// Apply it just as the standard jetty ContextProvider would do
LOG.info("Applying " + contextXmlUrl + " to " + _webApp);
XmlConfiguration xmlConfiguration = new XmlConfiguration(contextXmlUrl);
HashMap properties = new HashMap();
properties.put("Server", getDeploymentManager().getServer());
properties.put(OSGiWebappConstants.JETTY_BUNDLE_ROOT, rootResource.toString());
properties.put(OSGiServerConstants.JETTY_HOME, getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME));
xmlConfiguration.getProperties().putAll(properties);
xmlConfiguration.configure(_webApp);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
}
private File getFile (String file, File bundleInstall)
{
if (file == null)
return null;
if (file.startsWith("/") || file.startsWith("file:/")) //absolute location
return new File(file);
else
{
//relative location
//try inside the bundle first
File f = new File (bundleInstall, file);
if (f.exists()) return f;
String jettyHome = (String)getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
if (jettyHome != null)
return new File(jettyHome, file);
}
return null;
}
}
/* ------------------------------------------------------------ */
public AbstractWebAppProvider (ServerInstanceWrapper wrapper)
{
_serverWrapper = wrapper;
}
/* ------------------------------------------------------------ */
/**
* Get the parentLoaderPriority.
*
* @return the parentLoaderPriority
*/
public boolean isParentLoaderPriority()
{
return _parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/**
* Set the parentLoaderPriority.
*
* @param parentLoaderPriority the parentLoaderPriority to set
*/
public void setParentLoaderPriority(boolean parentLoaderPriority)
{
_parentLoaderPriority = parentLoaderPriority;
}
/* ------------------------------------------------------------ */
/**
* Get the defaultsDescriptor.
*
* @return the defaultsDescriptor
*/
public String getDefaultsDescriptor()
{
return _defaultsDescriptor;
}
/* ------------------------------------------------------------ */
/**
* Set the defaultsDescriptor.
*
* @param defaultsDescriptor the defaultsDescriptor to set
*/
public void setDefaultsDescriptor(String defaultsDescriptor)
{
_defaultsDescriptor = defaultsDescriptor;
}
/* ------------------------------------------------------------ */
public boolean isExtract()
{
return _extractWars;
}
/* ------------------------------------------------------------ */
public void setExtract(boolean extract)
{
_extractWars = extract;
}
/* ------------------------------------------------------------ */
/**
* @param tldBundles Comma separated list of bundles that contain tld jars
* that should be setup on the jetty instances created here.
*/
public void setTldBundles(String tldBundles)
{
_tldBundles = tldBundles;
}
/* ------------------------------------------------------------ */
/**
* @return The list of bundles that contain tld jars that should be setup on
* the jetty instances created here.
*/
public String getTldBundles()
{
return _tldBundles;
}
/* ------------------------------------------------------------ */
/**
* @param configurations The configuration class names.
*/
public void setConfigurationClasses(String[] configurations)
{
_configurationClasses = configurations == null ? null : (String[]) configurations.clone();
}
/* ------------------------------------------------------------ */
/**
*
*/
public String[] getConfigurationClasses()
{
return _configurationClasses;
}
/* ------------------------------------------------------------ */
public void setServerInstanceWrapper(ServerInstanceWrapper wrapper)
{
_serverWrapper = wrapper;
}
public ServerInstanceWrapper getServerInstanceWrapper()
{
return _serverWrapper;
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public DeploymentManager getDeploymentManager()
{
return _deploymentManager;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.deploy.AppProvider#setDeploymentManager(org.eclipse.jetty.deploy.DeploymentManager)
*/
public void setDeploymentManager(DeploymentManager deploymentManager)
{
_deploymentManager = deploymentManager;
}
/* ------------------------------------------------------------ */
public ContextHandler createContextHandler(App app) throws Exception
{
if (app == null)
return null;
if (!(app instanceof OSGiApp))
throw new IllegalStateException(app+" is not a BundleApp");
//Create a WebAppContext suitable to deploy in OSGi
ContextHandler ch = ((OSGiApp)app).createContextHandler();
return ch;
}
/* ------------------------------------------------------------ */
public static String getOriginId(Bundle contributor, String path)
{
return contributor.getSymbolicName() + "-" + contributor.getVersion().toString() + (path.startsWith("/") ? path : "/" + path);
}
}