// ========================================================================
// 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.
// ========================================================================
package org.eclipse.jetty.osgi.boot.internal.webapp;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
/**
* Helper to create a URL class-loader with the jars inside
* ${jetty.home}/lib/ext and ${jetty.home}/resources. In an ideal world, every
* library is an OSGi bundle that does loads nicely. To support standard jars or
* bundles that cannot be loaded in the current OSGi environment, we support
* inserting the jars in the usual jetty/lib/ext folders in the proper classpath
* for the webapps.
* <p>
* Also the folder resources typically contains central configuration files for
* things like: log config and others. We enable fragments to register classes
* that are called back and passed those resources to do what they need to do.
* </p>
* <p>
* For example the test-jndi webapplication depends on derby, derbytools,
* atomikos none of them are osgi bundles. we can either re-package them or we
* can place them in the usual lib/ext. <br/>
* In fact jasper's jsp libraries should maybe place in lib/ext too.
* </p>
* <p>
* The drawback is that those libraries will not be available in the OSGi
* classloader. Note that we could have setup those jars as embedded jars of the
* current bundle. However, we would need to know in advance what are those jars
* which was not acceptable. Also having those jars in a URLClassLoader seem to
* be required for some cases. For example jaspers' TldLocationsCache (replaced
* by TldScanner for servlet-3.0). <br/>
* Also all the dependencies of those libraries must be resolvable directly from
* the JettyBooStrapper bundle as it is set as the parent classloader. For
* example: if atomikos is placed in lib/ext it will work if and only if
* JettyBootStrapper import the necessary packages from javax.naming*,
* javax.transaction*, javax.mail* etc Most of the common cases of javax are
* added as optional import packages into jetty bootstrapper plugin. When there
* are not covered: please make a request or create a fragment or register a
* bundle with a buddy-policy onto the jetty bootstrapper..
* </p>
* <p>
* Alternatives to placing jars in lib/ext
* <ol>
* <li>Bundle the jars in an osgi bundle. Have the webapp(s) that context
* depends on them depend on that bundle. Things will go well for jetty.</li>
* <li>Bundle those jars in an osgi bundle-fragment that targets the
* jetty-bootstrap bundle</li>
* <li>Use equinox Buddy-Policy: register a buddy of the jetty bootstrapper
* bundle. (least favorite: it will work only on equinox)</li>
* </ol>
* </p>
*/
public class LibExtClassLoaderHelper
{
/**
* Class called back
*/
public interface IFilesInJettyHomeResourcesProcessor
{
void processFilesInResourcesFolder(File jettyHome, Map<String, File> filesInResourcesFolder);
}
public static Set<IFilesInJettyHomeResourcesProcessor> registeredFilesInJettyHomeResourcesProcessors = new HashSet<IFilesInJettyHomeResourcesProcessor>();
/**
* @param server
* @return a url classloader with the jars of resources, lib/ext and the
* jars passed in the other argument. The parent classloader usually
* is the JettyBootStrapper (an osgi classloader.
* @throws MalformedURLException
*/
public static ClassLoader createLibEtcClassLoader(File jettyHome, Server server,
ClassLoader parentClassLoader) throws MalformedURLException
{
if (jettyHome == null)
{
return parentClassLoader;
}
ArrayList<URL> urls = new ArrayList<URL>();
File jettyResources = new File(jettyHome,"resources");
if (jettyResources.exists())
{
// make sure it contains something else than README:
Map<String, File> jettyResFiles = new HashMap<String, File>();
for (File f : jettyResources.listFiles())
{
jettyResFiles.put(f.getName(),f);
if (f.getName().toLowerCase().startsWith("readme"))
{
continue;
}
else
{
if (urls.isEmpty())
{
urls.add(jettyResources.toURI().toURL());
}
}
}
processFilesInResourcesFolder(jettyHome,jettyResFiles);
}
File libExt = new File(jettyHome,"lib/ext");
if (libExt.exists())
{
for (File f : libExt.listFiles())
{
if (f.getName().endsWith(".jar"))
{
// cheap to tolerate folders so let's do it.
URL url = f.toURI().toURL();
if (f.isFile())
{// is this necessary anyways?
url = new URL("jar:" + url.toString() + "!/");
}
urls.add(url);
}
}
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]),parentClassLoader);
}
/**
* @param server
* @return a url classloader with the jars of resources, lib/ext and the
* jars passed in the other argument. The parent classloader usually
* is the JettyBootStrapper (an osgi classloader).
* If there was no extra jars to insert, then just return the parentClassLoader.
* @throws MalformedURLException
*/
public static ClassLoader createLibExtClassLoader(List<File> jarsContainerOrJars,
List<URL> otherJarsOrFolder, Server server,
ClassLoader parentClassLoader) throws MalformedURLException
{
if (jarsContainerOrJars == null && otherJarsOrFolder == null)
{
return parentClassLoader;
}
List<URL> urls = new ArrayList<URL>();
if (otherJarsOrFolder != null)
{
urls.addAll(otherJarsOrFolder);
}
if (jarsContainerOrJars != null)
{
for (File libExt : jarsContainerOrJars)
{
if (libExt.isDirectory())
{
for (File f : libExt.listFiles())
{
if (f.getName().endsWith(".jar"))
{
// cheap to tolerate folders so let's do it.
URL url = f.toURI().toURL();
if (f.isFile())
{// is this necessary anyways?
url = new URL("jar:" + url.toString() + "!/");
}
urls.add(url);
}
}
}
}
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]),parentClassLoader);
}
/**
* When we find files typically used for central logging configuration we do
* what it takes in this method to do what the user expects. Without
* depending too much directly on a particular logging framework.
* <p>
* We can afford to do some implementation specific code for a logging
* framework only in a fragment. <br/>
* Trying to configure log4j and logback in here.
* </p>
* <p>
* We recommend that slf4j jars are all placed in the osgi framework. And a
* single implementation if possible packaged as an osgi bundle is there.
* </p>
*/
protected static void processFilesInResourcesFolder(File jettyHome, Map<String, File> childrenFiles)
{
for (IFilesInJettyHomeResourcesProcessor processor : registeredFilesInJettyHomeResourcesProcessors)
{
processor.processFilesInResourcesFolder(jettyHome,childrenFiles);
}
}
}