blob: 3f83f1e1a1cf3bf800ac97692d6cc8cc48d774ff [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.internal.webapp;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.osgi.boot.utils.BundleClassLoaderHelperFactory;
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;
/**
* OSGiWebappClassLoader
*
*
* Extends the webapp classloader to also use the classloader of the Bundle defining the webapp.
*/
public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
{
private static final Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName());
/**
* when a logging framework is setup in the osgi classloaders, it can access
* this and register the classes that must not be found in the jar.
*/
public static final Set<String> JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED = new HashSet<String>();
public static void addClassThatIdentifiesAJarThatMustBeRejected(Class<?> zclass)
{
JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclass.getName().replace('.', '/') + ".class");
}
public static void addClassThatIdentifiesAJarThatMustBeRejected(String zclassName)
{
JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED.add(zclassName.replace('.', '/') + ".class");
}
static
{
addClassThatIdentifiesAJarThatMustBeRejected(HttpServlet.class);
}
private ClassLoader _osgiBundleClassLoader;
private Bundle _contributor;
private boolean _lookInOsgiFirst = true;
/* ------------------------------------------------------------ */
/**
* @param parent The parent classloader.
* @param context The WebAppContext
* @param contributor The bundle that defines this web-application.
* @throws IOException
*/
public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor)
throws IOException
{
super(parent, context);
_contributor = contributor;
_osgiBundleClassLoader = BundleClassLoaderHelperFactory.getFactory().getHelper().getBundleClassLoader(contributor);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return super.loadClass(name);
}
/* ------------------------------------------------------------ */
/**
* 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;
}
/* ------------------------------------------------------------ */
@Override
public Enumeration<URL> getResources(String name) throws IOException
{
Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
Enumeration<URL> urls = super.getResources(name);
if (_lookInOsgiFirst)
{
return Collections.enumeration(toList(osgiUrls, urls));
}
else
{
return Collections.enumeration(toList(urls, osgiUrls));
}
}
/* ------------------------------------------------------------ */
@Override
public URL getResource(String name)
{
if (_lookInOsgiFirst)
{
URL url = _osgiBundleClassLoader.getResource(name);
return url != null ? url : super.getResource(name);
}
else
{
URL url = super.getResource(name);
return url != null ? url : _osgiBundleClassLoader.getResource(name);
}
}
/* ------------------------------------------------------------ */
private List<URL> toList(Enumeration<URL> e, Enumeration<URL> e2)
{
List<URL> list = new ArrayList<URL>();
while (e != null && e.hasMoreElements())
list.add(e.nextElement());
while (e2 != null && e2.hasMoreElements())
list.add(e2.nextElement());
return list;
}
/* ------------------------------------------------------------ */
protected Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
return _lookInOsgiFirst ? _osgiBundleClassLoader.loadClass(name) : super.findClass(name);
}
catch (ClassNotFoundException cne)
{
try
{
return _lookInOsgiFirst ? super.findClass(name) : _osgiBundleClassLoader.loadClass(name);
}
catch (ClassNotFoundException cne2)
{
throw cne;
}
}
}
/* ------------------------------------------------------------ */
/**
* Parse the classpath ourselves to be able to filter things. This is a
* derivative work of the super class
*/
@Override
public void addClassPath(String classPath) throws IOException
{
StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
while (tokenizer.hasMoreTokens())
{
String path = tokenizer.nextToken();
Resource resource = getContext().newResource(path);
// Resolve file path if possible
File file = resource.getFile();
if (file != null && isAcceptableLibrary(file, JAR_WITH_SUCH_CLASS_MUST_BE_EXCLUDED))
{
super.addClassPath(path);
}
else
{
__logger.info("Did not add " + path + " to the classloader of the webapp " + getContext());
}
}
}
/* ------------------------------------------------------------ */
/**
* @param lib
* @return true if the lib should be included in the webapp classloader.
*/
private boolean isAcceptableLibrary(File file, Set<String> pathToClassFiles)
{
try
{
if (file.isDirectory())
{
for (String criteria : pathToClassFiles)
{
if (new File(file, criteria).exists()) { return false; }
}
}
else
{
JarFile jar = null;
try
{
jar = new JarFile(file);
for (String criteria : pathToClassFiles)
{
if (jar.getEntry(criteria) != null) { return false; }
}
}
finally
{
if (jar != null) try
{
jar.close();
}
catch (IOException ioe)
{
}
}
}
}
catch (IOException e)
{
// nevermind. just trying our best
__logger.ignore(e);
}
return true;
}
private static Field _contextField;
/* ------------------------------------------------------------ */
/**
* In the case of the generation of a webapp via a jetty context file we
* need a proper classloader to setup the app before we have the
* WebappContext So we place a fake one there to start with. We replace it
* with the actual webapp context with this method. We also apply the
* extraclasspath there at the same time.
*/
public void setWebappContext(WebAppContext webappContext)
{
try
{
if (_contextField == null)
{
_contextField = WebAppClassLoader.class.getDeclaredField("_context");
_contextField.setAccessible(true);
}
_contextField.set(this, webappContext);
if (webappContext.getExtraClasspath() != null)
{
addClassPath(webappContext.getExtraClasspath());
}
}
catch (Throwable t)
{
// humf that will hurt if it does not work.
__logger.warn("Unable to set webappcontext", t);
}
}
}