blob: e471a787c7c1d652c6fece51490fdad38b927445 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 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.jasper;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import javax.servlet.jsp.JspFactory;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
import org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.osgi.boot.utils.TldBundleDiscoverer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
/**
* ContainerTldBundleDiscoverer
*
* Finds bundles that are considered as on the container classpath that
* contain tlds.
*
* The System property org.eclipse.jetty.osgi.tldbundles is a comma
* separated list of exact symbolic names of bundles that have container classpath
* tlds.
*
* The DeploymentManager context attribute "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern"
* can be used to define a pattern of symbolic names of bundles that contain container
* classpath tlds.
*
* The matching bundles are converted to URLs that are put onto a special classloader that acts as the
* parent classloader for contexts deployed by the jetty Server instance (see ServerInstanceWrapper).
*
* It also discovers the bundle that contains the jstl taglib and adds it into the
* "org.eclipse.jetty.server.webapp.containerIncludeBundlePattern" (if it is not already there) so
* that the WebInfOSGiConfiguration class will add the jstl taglib bundle into the list of container
* resources.
*
* Eg:
* -Dorg.eclipse.jetty.osgi.tldbundles=org.springframework.web.servlet,com.opensymphony.module.sitemesh
*
*/
public class ContainerTldBundleDiscoverer implements TldBundleDiscoverer
{
private static final Logger LOG = Log.getLogger(ContainerTldBundleDiscoverer.class);
private static String DEFAULT_JSP_FACTORY_IMPL_CLASS = "org.apache.jasper.runtime.JspFactoryImpl";
/**
* Default name of a class that belongs to the jstl bundle. From that class
* we locate the corresponding bundle and register it as a bundle that
* contains tld files.
*/
private static String DEFAULT_JSTL_BUNDLE_CLASS = "org.apache.taglibs.standard.tag.rt.core.WhenTag";
private Bundle jstlBundle = null;
/**
* Check the System property "org.eclipse.jetty.osgi.tldbundles" for names of
* bundles that contain tlds and convert to URLs.
*
* @return The location of the jars that contain tld files as URLs.
*/
public URL[] getUrlsForBundlesWithTlds(DeploymentManager deploymentManager, BundleFileLocatorHelper locatorHelper) throws Exception
{
if (!isJspAvailable())
{
return new URL[0];
}
if (jstlBundle == null)
jstlBundle = findJstlBundle();
Bundle[] bundles = FrameworkUtil.getBundle(ContainerTldBundleDiscoverer.class).getBundleContext().getBundles();
HashSet<URL> urls = new HashSet<URL>();
String tmp = System.getProperty(OSGiWebInfConfiguration.SYS_PROP_TLD_BUNDLES); //comma separated exact names
List<String> sysNames = new ArrayList<String>();
if (tmp != null)
{
StringTokenizer tokenizer = new StringTokenizer(tmp, ", \n\r\t", false);
while (tokenizer.hasMoreTokens())
sysNames.add(tokenizer.nextToken());
}
tmp = (String) deploymentManager.getContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN); //bundle name patterns
Pattern pattern = (tmp==null? null : Pattern.compile(tmp));
//check that the jstl bundle is not already included in the pattern, and include it if it is not because
//subsequent classes such as OSGiWebInfConfiguration use this pattern to determine which jars are
//considered to be on the container classpath
if (jstlBundle != null)
{
if (pattern == null)
{
pattern = Pattern.compile(jstlBundle.getSymbolicName());
deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, jstlBundle.getSymbolicName());
}
else if (!(pattern.matcher(jstlBundle.getSymbolicName()).matches()))
{
String s = tmp+"|"+jstlBundle.getSymbolicName();
pattern = Pattern.compile(s);
deploymentManager.setContextAttribute(OSGiWebInfConfiguration.CONTAINER_BUNDLE_PATTERN, s);
}
}
for (Bundle bundle : bundles)
{
if (sysNames.contains(bundle.getSymbolicName()))
convertBundleLocationToURL(locatorHelper, bundle, urls);
else if (pattern != null && pattern.matcher(bundle.getSymbolicName()).matches())
convertBundleLocationToURL(locatorHelper, bundle, urls);
}
return urls.toArray(new URL[urls.size()]);
}
/**
* Check that jsp is on the classpath
* @return
*/
public boolean isJspAvailable()
{
try
{
getClass().getClassLoader().loadClass("org.apache.jasper.servlet.JspServlet");
}
catch (Exception e)
{
LOG.warn("Unable to locate the JspServlet: jsp support unavailable.", e);
return false;
}
return true;
}
/**
*
* Some versions of JspFactory do Class.forName, which probably won't work in an
* OSGi environment.
*/
public void fixJspFactory ()
{
try
{
Class<javax.servlet.ServletContext> servletContextClass = javax.servlet.ServletContext.class;
// bug #299733
JspFactory fact = JspFactory.getDefaultFactory();
if (fact == null)
{ // bug #299733
// JspFactory does a simple
// Class.getForName("org.apache.jasper.runtime.JspFactoryImpl")
// however its bundles does not import the jasper package
// so it fails. let's help things out:
fact = (JspFactory) JettyBootstrapActivator.class.getClassLoader().loadClass(DEFAULT_JSP_FACTORY_IMPL_CLASS).newInstance();
JspFactory.setDefaultFactory(fact);
}
}
catch (Exception e)
{
LOG.warn("Unable to set the JspFactory: jsp support incomplete.", e);
}
}
/**
* Find the bundle that contains a jstl implementation class, which assumes that
* the jstl taglibs will be inside the same bundle.
* @return
*/
public Bundle findJstlBundle ()
{
Class<?> jstlClass = null;
try
{
jstlClass = JSTLBundleDiscoverer.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
}
catch (ClassNotFoundException e)
{
LOG.info("jstl not on classpath", e);
}
if (jstlClass != null)
//get the bundle containing jstl
return FrameworkUtil.getBundle(jstlClass);
return null;
}
/**
* Resolves a bundle that contains tld files as a URL. The URLs are
* used by jasper to discover the tld files.
*
* 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.
*
* @param locatorHelper
* @param bundle
* @param urls
* @throws Exception
*/
private void convertBundleLocationToURL(BundleFileLocatorHelper locatorHelper, Bundle bundle, Set<URL> urls) throws Exception
{
File jasperLocation = locatorHelper.getBundleInstallLocation(bundle);
if (jasperLocation.isDirectory())
{
for (File f : jasperLocation.listFiles())
{
if (f.getName().endsWith(".jar") && f.isFile())
{
urls.add(f.toURI().toURL());
}
else if (f.isDirectory() && f.getName().equals("lib"))
{
for (File f2 : jasperLocation.listFiles())
{
if (f2.getName().endsWith(".jar") && f2.isFile())
{
urls.add(f2.toURI().toURL());
}
}
}
}
urls.add(jasperLocation.toURI().toURL());
}
else
{
urls.add(jasperLocation.toURI().toURL());
}
}
}