Issue #5133 - Reworking WebAppContext.extraClasspath

+ Now parsed by WebAppContext into List<Resource>
+ Reintroduced Resource.fromList
+ Refactored ResourceFactory to never return null
  and always throw an exception if unable to
  get/create/resolve the Resource

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Joakim Erdfelt 2020-08-12 12:50:02 -05:00
parent ccdd20b7bc
commit 0b0d7d3282
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
15 changed files with 278 additions and 278 deletions

View File

@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.objectweb.asm.Opcodes;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
@ -145,7 +144,7 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa
}
});
boolean hasDotPath = false;
StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, ",;", false);
StringTokenizer tokenizer = new StringTokenizer(bundleClasspath, StringUtil.DEFAULT_DELIMS, false);
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();

View File

@ -233,7 +233,7 @@ public class ServerInstanceWrapper
Thread.currentThread().setContextClassLoader(libExtClassLoader);
String jettyConfigurationUrls = (String)props.get(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS);
List<URL> jettyConfigurations = jettyConfigurationUrls != null ? Util.fileNamesAsURLs(jettyConfigurationUrls, Util.DEFAULT_DELIMS) : null;
List<URL> jettyConfigurations = jettyConfigurationUrls != null ? Util.fileNamesAsURLs(jettyConfigurationUrls, StringUtil.DEFAULT_DELIMS) : null;
_server = configure(server, jettyConfigurations, props);
@ -418,7 +418,7 @@ public class ServerInstanceWrapper
List<URL> libURLs = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(sharedURLs, ",;", false);
StringTokenizer tokenizer = new StringTokenizer(sharedURLs, StringUtil.DEFAULT_DELIMS, false);
while (tokenizer.hasMoreTokens())
{
String tok = tokenizer.nextToken();

View File

@ -23,12 +23,10 @@ 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;
@ -201,22 +199,16 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
@Override
public void addClassPath(String classPath) throws IOException
{
StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
while (tokenizer.hasMoreTokens())
for (Resource resource : Resource.fromList(classPath, false, (path) -> getContext().newResource(path)))
{
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);
super.addClassPath(resource);
}
else
{
LOG.info("Did not add " + path + " to the classloader of the webapp " + getContext());
LOG.info("Did not add {} to the classloader of the webapp {}", resource, getContext());
}
}
}
@ -274,35 +266,4 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
}
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.
*
* @param webappContext the web app context
*/
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.
LOG.warn("Unable to set webappcontext", t);
}
}
}

View File

@ -35,8 +35,6 @@ import org.osgi.framework.InvalidSyntaxException;
*/
public class Util
{
public static final String DEFAULT_DELIMS = ",;";
/**
* Create an osgi filter for the given classname and server name.
*
@ -101,7 +99,7 @@ public class Util
public static List<URL> fileNamesAsURLs(String val, String delims)
throws Exception
{
String separators = DEFAULT_DELIMS;
String separators = StringUtil.DEFAULT_DELIMS;
if (delims == null)
delims = separators;

View File

@ -188,8 +188,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
if (_parent != null)
{
HttpContent httpContent = _parent.getContent(pathInContext, maxBufferSize);
if (httpContent != null)
return httpContent;
return httpContent;
}
return null;
@ -234,18 +233,26 @@ public class CachedContentFactory implements HttpContent.ContentFactory
if (compressedContent == null || compressedContent.isValid())
{
compressedContent = null;
Resource compressedResource = _factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified() >= resource.lastModified() &&
compressedResource.length() < resource.length())
try
{
compressedContent = new CachedHttpContent(compressedPathInContext, compressedResource, null);
CachedHttpContent added = _cache.putIfAbsent(compressedPathInContext, compressedContent);
if (added != null)
Resource compressedResource = _factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified() >= resource.lastModified() &&
compressedResource.length() < resource.length())
{
compressedContent.invalidate();
compressedContent = added;
compressedContent = new CachedHttpContent(compressedPathInContext, compressedResource, null);
CachedHttpContent added = _cache.putIfAbsent(compressedPathInContext, compressedContent);
if (added != null)
{
compressedContent.invalidate();
compressedContent = added;
}
}
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to find compressed path in context: {}", compressedPathInContext, e);
}
}
if (compressedContent != null)
precompresssedContents.put(format, compressedContent);
@ -279,12 +286,20 @@ public class CachedContentFactory implements HttpContent.ContentFactory
if (compressedContent != null && compressedContent.isValid() && compressedContent.getResource().lastModified() >= resource.lastModified())
compressedContents.put(format, compressedContent);
// Is there a precompressed resource?
Resource compressedResource = _factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified() >= resource.lastModified() &&
compressedResource.length() < resource.length())
compressedContents.put(format,
new ResourceHttpContent(compressedResource, _mimeTypes.getMimeByExtension(compressedPathInContext), maxBufferSize));
try
{
// Is there a precompressed resource?
Resource compressedResource = _factory.getResource(compressedPathInContext);
if (compressedResource.exists() && compressedResource.lastModified() >= resource.lastModified() &&
compressedResource.length() < resource.length())
compressedContents.put(format,
new ResourceHttpContent(compressedResource, _mimeTypes.getMimeByExtension(compressedPathInContext), maxBufferSize));
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to find compressed path in context: {}", compressedPathInContext, e);
}
}
if (!compressedContents.isEmpty())
return new ResourceHttpContent(resource, mt, maxBufferSize, compressedContents);

View File

@ -51,14 +51,12 @@ public class ResourceContentFactory implements ContentFactory
@Override
public HttpContent getContent(String pathInContext, int maxBufferSize)
throws IOException
{
try
{
// try loading the content from our factory.
Resource resource = _factory.getResource(pathInContext);
HttpContent loaded = load(pathInContext, resource, maxBufferSize);
return loaded;
return load(pathInContext, resource, maxBufferSize);
}
catch (Throwable t)
{

View File

@ -20,7 +20,7 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -74,7 +74,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
{
}
});
_resourceService.setGzipEquivalentFileExtensions(new ArrayList<>(Arrays.asList(new String[]{".svgz"})));
_resourceService.setGzipEquivalentFileExtensions(new ArrayList<>(Collections.singletonList(".svgz")));
}
@Override
@ -86,9 +86,18 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
for (int i = 0; i < _welcomes.length; i++)
{
String welcomeInContext = URIUtil.addPaths(pathInContext, _welcomes[i]);
Resource welcome = getResource(welcomeInContext);
if (welcome != null && welcome.exists())
return welcomeInContext;
try
{
Resource welcome = getResource(welcomeInContext);
if (welcome.exists())
return welcomeInContext;
}
catch (IOException e)
{
// this happens on a critical failure of Resource
if (LOG.isDebugEnabled())
LOG.debug("Failed to resolve welcome file: {}", welcomeInContext);
}
}
// not found
return null;
@ -140,13 +149,19 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
}
@Override
public Resource getResource(String path)
public Resource getResource(String path) throws IOException
{
if (LOG.isDebugEnabled())
LOG.debug("{} getResource({})", _context == null ? _baseResource : _context, _baseResource, path);
LOG.debug("{} newResource({}): baseResource:{}", _context == null ? _baseResource : _context, path, _baseResource);
if (path == null || !path.startsWith("/"))
return null;
if (path == null)
{
throw new IOException("null path");
}
else if (!path.startsWith("/"))
{
throw new IOException("Invalid path reference: " + path);
}
try
{
@ -161,7 +176,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
{
if (LOG.isDebugEnabled())
LOG.debug("resource={} alias={}", r, r.getAlias());
return null;
throw new IOException("Unacceptable alias reference: " + r);
}
}
else if (_context != null)
@ -170,14 +185,15 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
r = getStylesheet();
return r;
if (r != null)
return r;
}
catch (Exception e)
{
LOG.debug("Unable to get Resource for {}", path, e);
}
return null;
throw new IOException("Unable to find Resource for " + path);
}
/**

View File

@ -37,6 +37,7 @@ public class StringUtil
public static final String ALL_INTERFACES = "0.0.0.0";
public static final String CRLF = "\r\n";
public static final String DEFAULT_DELIMS = ",;";
public static final String __ISO_8859_1 = "iso-8859-1";
public static final String __UTF8 = "utf-8";

View File

@ -34,10 +34,13 @@ import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Loader;
@ -419,22 +422,11 @@ public abstract class Resource implements ResourceFactory, Closeable
/**
* Get a resource from within this resource.
* <p>
* This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
* This method satisfied the {@link ResourceFactory} interface.
*/
@Override
public Resource getResource(String path)
public Resource getResource(String path) throws IOException
{
try
{
return addPath(path);
}
catch (Exception e)
{
LOG.debug("Unable to addPath", e);
return null;
}
return addPath(path);
}
// FIXME: this appears to not be used
@ -921,4 +913,97 @@ public abstract class Resource implements ResourceFactory, Closeable
{
return file.toURI().toURL();
}
/**
* Parse a list of String delimited resources and
* return the List of Resources instances it represents.
* <p>
* Supports glob references that end in {@code /*} or {@code \*}.
* Glob references will only iterate through the level specified and will not traverse
* found directories within the glob reference.
* </p>
*
* @param delimitedReferences the comma {@code ,} or semicolon {@code ;} delimited
* String of resource references.
* @param globDirs true if glob references return directories within the glob as well
* @return the list of resources parsed from input string.
*/
public static List<Resource> fromList(String delimitedReferences, boolean globDirs) throws IOException
{
return fromList(delimitedReferences, globDirs, Resource::newResource);
}
/**
* Parse a delimited String of resource references and
* return the List of Resources instances it represents.
* <p>
* Supports glob references that end in {@code /*} or {@code \*}.
* Glob references will only iterate through the level specified and will not traverse
* found directories within the glob reference.
* </p>
*
* @param delimitedReferences the comma {@code ,} or semicolon {@code ;} delimited
* String of resource references.
* @param globDirs true if glob references return directories within the glob as well
* @param resourceFactory the ResourceFactory used to create new Resource references
* @return the list of resources parsed from input string.
*/
public static List<Resource> fromList(String delimitedReferences, boolean globDirs, ResourceFactory resourceFactory) throws IOException
{
if (StringUtil.isBlank(delimitedReferences))
{
return Collections.emptyList();
}
List<Resource> resources = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(delimitedReferences, StringUtil.DEFAULT_DELIMS);
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
// Is this a glob reference?
if (token.endsWith("/*") || token.endsWith("\\*"))
{
String dir = token.substring(0, token.length() - 2);
// Use directory
Resource dirResource = resourceFactory.getResource(dir);
if (dirResource.exists() && dirResource.isDirectory())
{
// To obtain the list of entries
String[] entries = dirResource.list();
if (entries != null)
{
Arrays.sort(entries);
for (String entry : entries)
{
try
{
Resource resource = dirResource.addPath(entry);
if (!resource.isDirectory())
{
resources.add(resource);
}
else if (globDirs)
{
resources.add(resource);
}
}
catch (Exception ex)
{
LOG.warn("Bad glob [{}] entry: {}", token, entry, ex);
}
}
}
}
}
else
{
// Simple reference, add as-is
resources.add(resourceFactory.getResource(token));
}
}
return resources;
}
}

View File

@ -29,8 +29,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
/**
@ -131,8 +131,9 @@ public class ResourceCollection extends Resource
* Instantiates a new resource collection.
*
* @param csvResources the string containing comma-separated resource strings
* @throws IOException if any listed resource is not valid
*/
public ResourceCollection(String csvResources)
public ResourceCollection(String csvResources) throws IOException
{
setResourcesAsCSV(csvResources);
}
@ -147,6 +148,22 @@ public class ResourceCollection extends Resource
return _resources;
}
/**
* Sets the resource collection's resources.
*
* @param res the resources to set
*/
public void setResources(List<Resource> res)
{
if (res.isEmpty())
{
_resources = null;
return;
}
_resources = res.toArray(new Resource[0]);
}
/**
* Sets the resource collection's resources.
*
@ -167,13 +184,7 @@ public class ResourceCollection extends Resource
res.add(resource);
}
if (res.isEmpty())
{
_resources = null;
return;
}
_resources = res.toArray(new Resource[0]);
setResources(res);
}
/**
@ -182,56 +193,22 @@ public class ResourceCollection extends Resource
*
* @param csvResources the comma-separated string containing
* one or more resource strings.
* @throws IOException if unable resource declared is not valid
*/
public void setResourcesAsCSV(String csvResources)
public void setResourcesAsCSV(String csvResources) throws IOException
{
if (csvResources == null)
if (StringUtil.isBlank(csvResources))
{
throw new IllegalArgumentException("CSV String is null");
}
StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
int len = tokenizer.countTokens();
if (len == 0)
{
throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
" argument must be a string containing one or more comma-separated resource strings.");
throw new IllegalArgumentException("CSV String is blank");
}
List<Resource> res = new ArrayList<>();
try
for (Resource resource : Resource.fromList(csvResources, false))
{
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
// TODO: If we want to support CSV tokens with spaces then we should not trim here
// However, if we decide to to this, then CVS formatting/syntax becomes more strict.
if (token.length() == 0)
{
continue; // skip
}
Resource resource = Resource.newResource(token);
assertResourceValid(resource);
res.add(resource);
}
if (res.isEmpty())
{
_resources = null;
return;
}
_resources = res.toArray(new Resource[0]);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
assertResourceValid(resource);
res.add(resource);
}
setResources(res);
}
/**

View File

@ -18,17 +18,26 @@
package org.eclipse.jetty.util.resource;
import java.io.IOException;
/**
* ResourceFactory.
*/
public interface ResourceFactory
{
/**
* Get a resource for a path.
* Get a Resource from a provided String.
* <p>
* The behavior here is dependent on the
* implementation of ResourceFactory.
* The provided path can be resolved
* against a known Resource, or can
* be a from-scratch Resource.
* </p>
*
* @param path The path to the resource
* @return The resource or null
* @return The resource
* @throws IOException if unable to create Resource
*/
Resource getResource(String path);
Resource getResource(String path) throws IOException;
}

View File

@ -113,7 +113,7 @@ public class ResourceCollectionTest
}
@Test
public void testSetResourceNullThrowsISE()
public void testSetResourceArrayNullThrowsISE()
{
// Create a ResourceCollection with one valid entry
Path path = MavenTestingUtils.getTargetPath();
@ -121,7 +121,7 @@ public class ResourceCollectionTest
ResourceCollection coll = new ResourceCollection(resource);
// Reset collection to invalid state
coll.setResources(null);
coll.setResources((Resource[])null);
assertThrowIllegalStateException(coll);
}

View File

@ -36,13 +36,12 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.PatternMatcher;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.EmptyResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
@ -802,7 +801,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
*
* @param context the context to find extra classpath jars in
* @return the list of Resources with the extra classpath, or null if not found
* @throws Exception if unable to find the extra classpath jars
* @throws Exception if unable to resolve the extra classpath jars
*/
protected List<Resource> findExtraClasspathJars(WebAppContext context)
throws Exception
@ -810,55 +809,10 @@ public class MetaInfConfiguration extends AbstractConfiguration
if (context == null || context.getExtraClasspath() == null)
return null;
List<Resource> jarResources = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
// Is this a Glob Reference?
if (isGlobReference(token))
{
String dir = token.substring(0, token.length() - 2);
// Use directory
Resource dirResource = context.newResource(dir);
if (dirResource.exists() && dirResource.isDirectory())
{
// To obtain the list of files
String[] entries = dirResource.list();
if (entries != null)
{
Arrays.sort(entries);
for (String entry : entries)
{
try
{
Resource fileResource = dirResource.addPath(entry);
if (isFileSupported(fileResource))
{
jarResources.add(fileResource);
}
}
catch (Exception ex)
{
LOG.warn(Log.EXCEPTION, ex);
}
}
}
}
}
else
{
// Simple reference, add as-is
Resource resource = context.newResource(token);
if (isFileSupported(resource))
{
jarResources.add(resource);
}
}
}
return jarResources;
return context.getExtraClasspath()
.stream()
.filter(this::isFileSupported)
.collect(Collectors.toList());
}
/**
@ -892,7 +846,7 @@ public class MetaInfConfiguration extends AbstractConfiguration
*
* @param context the context to look for extra classpaths in
* @return the list of Resources to the extra classpath
* @throws Exception if unable to find the extra classpaths
* @throws Exception if unable to resolve the extra classpath resources
*/
protected List<Resource> findExtraClasspathDirs(WebAppContext context)
throws Exception
@ -900,23 +854,10 @@ public class MetaInfConfiguration extends AbstractConfiguration
if (context == null || context.getExtraClasspath() == null)
return null;
List<Resource> dirResources = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(context.getExtraClasspath(), ",;");
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
// ignore glob references, they only refer to lists of jars/zips anyway
if (!isGlobReference(token))
{
Resource resource = context.newResource(token);
if (resource.exists() && resource.isDirectory())
{
dirResources.add(resource);
}
}
}
return dirResources;
return context.getExtraClasspath()
.stream()
.filter(Resource::isDirectory)
.collect(Collectors.toList());
}
private String uriJarPrefix(URI uri, String suffix)
@ -932,13 +873,23 @@ public class MetaInfConfiguration extends AbstractConfiguration
}
}
private boolean isGlobReference(String token)
{
return token.endsWith("/*") || token.endsWith("\\*");
}
private boolean isFileSupported(Resource resource)
{
try
{
if (resource.isDirectory())
return false;
if (resource.getFile() == null)
return false;
}
catch (Throwable t)
{
if (LOG.isDebugEnabled())
LOG.debug("Bad Resource reference: {}", resource, t);
return false;
}
String filenameLowercase = resource.getName().toLowerCase(Locale.ENGLISH);
int dot = filenameLowercase.lastIndexOf('.');
String extension = (dot < 0 ? null : filenameLowercase.substring(dot));

View File

@ -41,6 +41,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.ClassVisibilityChecker;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
@ -112,7 +113,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
*/
boolean isParentLoaderPriority();
String getExtraClasspath();
List<Resource> getExtraClasspath();
boolean isServerResource(String name, URL parentUrl);
@ -185,7 +186,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
if (extensions != null)
{
StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
StringTokenizer tokenizer = new StringTokenizer(extensions, StringUtil.DEFAULT_DELIMS);
while (tokenizer.hasMoreTokens())
{
_extensions.add(tokenizer.nextToken().trim());
@ -193,7 +194,12 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
if (context.getExtraClasspath() != null)
addClassPath(context.getExtraClasspath());
{
for (Resource resource : context.getExtraClasspath())
{
addClassPath(resource);
}
}
}
/**
@ -235,7 +241,23 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
else
{
addClassPath(resource.toString());
// Resolve file path if possible
File file = resource.getFile();
if (file != null)
{
URL url = resource.getURI().toURL();
addURL(url);
}
else if (resource.isDirectory())
{
addURL(resource.getURI().toURL());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Check file exists and is not nested jar: " + resource);
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
}
}
}
@ -251,53 +273,9 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
if (classPath == null)
return;
StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
while (tokenizer.hasMoreTokens())
for (Resource resource : Resource.fromList(classPath, false, _context::newResource))
{
String token = tokenizer.nextToken().trim();
if (token.endsWith("*"))
{
if (token.length() > 1)
{
token = token.substring(0, token.length() - 1);
Resource resource = _context.newResource(token);
if (LOG.isDebugEnabled())
LOG.debug("Glob Path resource=" + resource);
resource = _context.newResource(token);
addJars(resource);
}
return;
}
Resource resource = _context.newResource(token);
if (LOG.isDebugEnabled())
LOG.debug("Path resource=" + resource);
if (resource.isDirectory() && resource instanceof ResourceCollection)
{
addClassPath(resource);
}
else
{
// Resolve file path if possible
File file = resource.getFile();
if (file != null)
{
URL url = resource.getURI().toURL();
addURL(url);
}
else if (resource.isDirectory())
{
addURL(resource.getURI().toURL());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Check file exists and is not nested jar: " + resource);
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
}
}
addClassPath(resource);
}
}

View File

@ -204,7 +204,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
private boolean _persistTmpDir = false;
private String _war;
private String _extraClasspath;
private List<Resource> _extraClasspath;
private Throwable _unavailableException;
private Map<String, String> _resourceAliases;
@ -1227,17 +1227,29 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
@Override
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
public String getExtraClasspath()
public List<Resource> getExtraClasspath()
{
return _extraClasspath;
}
/**
* Set the Extra ClassPath via delimited String.
* <p>
* This is a convenience method for {@link #setExtraClasspath(List)}
* </p>
*
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
* pointing to directories or jar files. Directories should end
* with '/'.
* @throws IOException if unable to resolve the resources referenced
* @see #setExtraClasspath(List)
*/
public void setExtraClasspath(String extraClasspath)
public void setExtraClasspath(String extraClasspath) throws IOException
{
setExtraClasspath(Resource.fromList(extraClasspath, false, this::newResource));
}
public void setExtraClasspath(List<Resource> extraClasspath)
{
_extraClasspath = extraClasspath;
}