// ========================================================================
// 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.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.BundleClassLoaderHelper;
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;
/**
* Extends the webappclassloader to insert the classloader provided by the osgi
* bundle at the same level than any other jars palced in the webappclassloader.
*/
public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleReference
{
private Logger __logger = Log.getLogger(OSGiWebappClassLoader.class.getName().toString());
/**
* 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 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;
private Set<String> _libsAlreadyInManifest = new HashSet<String>();
/**
* @param parent The parent classloader. In this case
* @param context The WebAppContext
* @param contributor The bundle that defines this web-application.
* @throws IOException
*/
public OSGiWebappClassLoader(ClassLoader parent, WebAppContext context, Bundle contributor,
BundleClassLoaderHelper bundleClassLoaderHelper) throws IOException
{
super(parent,context);
_contributor = contributor;
_osgiBundleClassLoader = bundleClassLoaderHelper.getBundleClassLoader(contributor);
}
/**
* 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;
}
/**
* Reads the manifest. If the manifest is already configured to loads a few
* libs we should not add them to the classpath of the webapp. Not really
* important as we resolve classes through the osgi classloader first and
* then default on the libs of the webapp.
*/
private void computeLibsAlreadyInOSGiClassLoader()
{
// TODO
}
@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
e.printStackTrace();
}
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.
t.printStackTrace();
}
}
}