Merge branch 'jetty-10.0.x-5133-webappcontext-extraclasspath-cleanup' into jetty-10.0.x

This commit is contained in:
Joakim Erdfelt 2020-09-22 12:24:42 -05:00
commit dc6bee5c96
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
21 changed files with 356 additions and 379 deletions

View File

@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import org.eclipse.jetty.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
@ -337,17 +338,8 @@ public class WebAppPropertyConverter
* @param resources the resources to convert
* @return csv string of resource filenames
*/
private static String toCSV(Resource[] resources)
private static String toCSV(List<Resource> resources)
{
StringBuilder rb = new StringBuilder();
for (Resource r : resources)
{
if (rb.length() > 0)
rb.append(",");
rb.append(r.toString());
}
return rb.toString();
return resources.stream().map(Object::toString).collect(Collectors.joining(","));
}
}

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

@ -20,15 +20,12 @@ 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;
@ -201,22 +198,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 {} to the classloader of the webapp {}", path, getContext());
LOG.info("Did not add {} to the classloader of the webapp {}", resource, getContext());
}
}
}
@ -272,37 +263,4 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
}
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.
*
* @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

@ -210,7 +210,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
return (len > 0 && (_useFileMappedBuffer || (len < _maxCachedFileSize && len < _maxCacheSize)));
}
private HttpContent load(String pathInContext, Resource resource, int maxBufferSize)
private HttpContent load(String pathInContext, Resource resource, int maxBufferSize) throws IOException
{
if (resource == null || !resource.exists())
return null;

View File

@ -50,20 +50,26 @@ public class ResourceContentFactory implements ContentFactory
}
@Override
public HttpContent getContent(String pathInContext, int maxBufferSize)
throws IOException
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)
{
// Any error has potential to reveal fully qualified path
throw (InvalidPathException)new InvalidPathException(pathInContext, "Invalid PathInContext").initCause(t);
// There are many potential Exceptions that can reveal a fully qualified path.
// See Issue #2560 - Always wrap a Throwable here in an InvalidPathException
// that is limited to only the provided pathInContext.
// The cause (which might reveal a fully qualified path) is still available,
// on the Exception and the logging, but is not reported in normal error page situations.
// This specific exception also allows WebApps to specifically hook into a known / reliable
// Exception type for ErrorPageErrorHandling logic.
InvalidPathException saferException = new InvalidPathException(pathInContext, "Invalid PathInContext");
saferException.initCause(t);
throw saferException;
}
}

View File

@ -881,6 +881,6 @@ public class ResourceService
* @param pathInContext the path of the request
* @return The path of the matching welcome file in context or null.
*/
String getWelcomeFile(String pathInContext);
String getWelcomeFile(String pathInContext) throws IOException;
}
}

View File

@ -1896,6 +1896,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return Collections.unmodifiableMap(_localeEncodingMap);
}
/**
* Attempt to get a Resource from the Context.
*
* @param path the path within the resource to attempt to get
* @return the resource, or null if not available.
* @throws MalformedURLException if unable to form a Resource from the provided path
*/
public Resource getResource(String path) throws MalformedURLException
{
if (path == null || !path.startsWith(URIUtil.SLASH))

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server.handler;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -36,6 +37,7 @@ import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.server.ResourceService.WelcomeFactory;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
@ -78,7 +80,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
}
@Override
public String getWelcomeFile(String pathInContext)
public String getWelcomeFile(String pathInContext) throws IOException
{
if (_welcomes == null)
return null;
@ -87,7 +89,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,
{
String welcomeInContext = URIUtil.addPaths(pathInContext, _welcomes[i]);
Resource welcome = getResource(welcomeInContext);
if (welcome != null && welcome.exists())
if (welcome.exists())
return welcomeInContext;
}
// not found
@ -140,44 +142,51 @@ 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, path);
if (path == null || !path.startsWith("/"))
return null;
try
if (StringUtil.isBlank(path))
{
Resource r = null;
throw new IllegalArgumentException("Path is blank");
}
if (_baseResource != null)
if (!path.startsWith("/"))
{
throw new IllegalArgumentException("Path reference invalid: " + path);
}
Resource r = null;
if (_baseResource != null)
{
path = URIUtil.canonicalPath(path);
r = _baseResource.addPath(path);
if (r.isAlias() && (_context == null || !_context.checkAlias(path, r)))
{
path = URIUtil.canonicalPath(path);
r = _baseResource.addPath(path);
if (r != null && r.isAlias() && (_context == null || !_context.checkAlias(path, r)))
{
if (LOG.isDebugEnabled())
LOG.debug("resource={} alias={}", r, r.getAlias());
return null;
}
if (LOG.isDebugEnabled())
LOG.debug("Rejected alias resource={} alias={}", r, r.getAlias());
throw new IllegalStateException("Rejected alias reference: " + path);
}
else if (_context != null)
r = _context.getResource(path);
if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
r = getStylesheet();
return r;
}
catch (Exception e)
else if (_context != null)
{
LOG.debug("Unable to get Resource for {}", path, e);
r = _context.getResource(path);
if (r != null)
return r;
}
return null;
if ((r == null || !r.exists()) && path.endsWith("/jetty-dir.css"))
r = getStylesheet();
if (r == null)
{
throw new FileNotFoundException("Resource: " + path);
}
return r;
}
/**

View File

@ -26,6 +26,7 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
@ -110,7 +111,7 @@ public class ResourceCacheTest
}
@Test
public void testMutlipleSources1() throws Exception
public void testMultipleSources1() throws Exception
{
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
@ -119,12 +120,12 @@ public class ResourceCacheTest
new PathResource(basePath.resolve("two")),
new PathResource(basePath.resolve("three")));
Resource[] r = rc.getResources();
List<Resource> r = rc.getResources();
MimeTypes mime = new MimeTypes();
CachedContentFactory rc3 = new CachedContentFactory(null, r[2], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc2 = new CachedContentFactory(rc3, r[1], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc1 = new CachedContentFactory(rc2, r[0], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc3 = new CachedContentFactory(null, r.get(2), mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc2 = new CachedContentFactory(rc3, r.get(1), mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc1 = new CachedContentFactory(rc2, r.get(0), mime, false, false, CompressedContentFormat.NONE);
assertEquals(getContent(rc1, "1.txt"), "1 - one");
assertEquals(getContent(rc1, "2.txt"), "2 - two");
@ -149,11 +150,11 @@ public class ResourceCacheTest
new PathResource(basePath.resolve("two")),
new PathResource(basePath.resolve("three")));
Resource[] r = rc.getResources();
List<Resource> r = rc.getResources();
MimeTypes mime = new MimeTypes();
CachedContentFactory rc3 = new CachedContentFactory(null, r[2], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc2 = new CachedContentFactory(rc3, r[1], mime, false, false, CompressedContentFormat.NONE)
CachedContentFactory rc3 = new CachedContentFactory(null, r.get(2), mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc2 = new CachedContentFactory(rc3, r.get(1), mime, false, false, CompressedContentFormat.NONE)
{
@Override
public boolean isCacheable(Resource resource)
@ -162,7 +163,7 @@ public class ResourceCacheTest
}
};
CachedContentFactory rc1 = new CachedContentFactory(rc2, r[0], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory rc1 = new CachedContentFactory(rc2, r.get(0), mime, false, false, CompressedContentFormat.NONE);
assertEquals(getContent(rc1, "1.txt"), "1 - one");
assertEquals(getContent(rc1, "2.txt"), "2 - two");

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

@ -124,6 +124,6 @@ public class EmptyResource extends Resource
@Override
public Resource addPath(String path) throws IOException, MalformedURLException
{
return null;
return this;
}
}

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;
@ -412,7 +415,7 @@ public abstract class Resource implements ResourceFactory, Closeable
* given name.
*
* @param path The path segment to add, which is not encoded
* @return the Resource for the resolved path within this Resource.
* @return the Resource for the resolved path within this Resource, never null
* @throws IOException if unable to resolve the path
* @throws MalformedURLException if the resolution of the path fails because the input path parameter is malformed.
*/
@ -421,22 +424,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
@ -923,4 +915,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 resources the comma {@code ,} or semicolon {@code ;} delimited
* String of resource references.
* @param globDirs true to return directories in addition to files at the level of the glob
* @return the list of resources parsed from input string.
*/
public static List<Resource> fromList(String resources, boolean globDirs) throws IOException
{
return fromList(resources, 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 resources the comma {@code ,} or semicolon {@code ;} delimited
* String of resource references.
* @param globDirs true to return directories in addition to files at the level of the glob
* @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 resources, boolean globDirs, ResourceFactory resourceFactory) throws IOException
{
if (StringUtil.isBlank(resources))
{
return Collections.emptyList();
}
List<Resource> returnedResources = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(resources, 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())
{
returnedResources.add(resource);
}
else if (globDirs)
{
returnedResources.add(resource);
}
}
catch (Exception ex)
{
LOG.warn("Bad glob [{}] entry: {}", token, entry, ex);
}
}
}
}
}
else
{
// Simple reference, add as-is
returnedResources.add(resourceFactory.getResource(token));
}
}
return returnedResources;
}
}

View File

@ -25,12 +25,12 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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;
/**
@ -42,7 +42,7 @@ import org.eclipse.jetty.util.URIUtil;
*/
public class ResourceCollection extends Resource
{
private Resource[] _resources;
private List<Resource> _resources;
/**
* Instantiates an empty resource collection.
@ -51,7 +51,7 @@ public class ResourceCollection extends Resource
*/
public ResourceCollection()
{
_resources = new Resource[0];
_resources = new ArrayList<>();
}
/**
@ -61,7 +61,7 @@ public class ResourceCollection extends Resource
*/
public ResourceCollection(Resource... resources)
{
List<Resource> list = new ArrayList<>();
_resources = new ArrayList<>();
for (Resource r : resources)
{
if (r == null)
@ -70,15 +70,25 @@ public class ResourceCollection extends Resource
}
if (r instanceof ResourceCollection)
{
Collections.addAll(list, ((ResourceCollection)r).getResources());
_resources.addAll(((ResourceCollection)r).getResources());
}
else
{
assertResourceValid(r);
list.add(r);
_resources.add(r);
}
}
_resources = list.toArray(new Resource[0]);
}
/**
* Instantiates a new resource collection.
*
* @param resources the resources to be added to collection
*/
public ResourceCollection(Collection<Resource> resources)
{
_resources = new ArrayList<>();
_resources.addAll(resources);
}
/**
@ -88,14 +98,13 @@ public class ResourceCollection extends Resource
*/
public ResourceCollection(String[] resources)
{
_resources = new ArrayList<>();
if (resources == null || resources.length == 0)
{
_resources = null;
return;
}
ArrayList<Resource> res = new ArrayList<>();
try
{
for (String strResource : resources)
@ -106,16 +115,13 @@ public class ResourceCollection extends Resource
}
Resource resource = Resource.newResource(strResource);
assertResourceValid(resource);
res.add(resource);
_resources.add(resource);
}
if (res.isEmpty())
if (_resources.isEmpty())
{
_resources = null;
return;
throw new IllegalArgumentException("resources cannot be empty or null");
}
_resources = res.toArray(new Resource[0]);
}
catch (RuntimeException e)
{
@ -131,22 +137,39 @@ 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);
setResources(csvResources);
}
/**
* Retrieves the resource collection's resources.
*
* @return the resource array
* @return the resource collection
*/
public Resource[] getResources()
public List<Resource> getResources()
{
return _resources;
}
/**
* Sets the resource collection's resources.
*
* @param res the resources to set
*/
public void setResources(List<Resource> res)
{
_resources = new ArrayList<>();
if (res.isEmpty())
{
return;
}
_resources.addAll(res);
}
/**
* Sets the resource collection's resources.
*
@ -167,71 +190,37 @@ public class ResourceCollection extends Resource
res.add(resource);
}
if (res.isEmpty())
{
_resources = null;
return;
}
_resources = res.toArray(new Resource[0]);
setResources(res);
}
/**
* Sets the resources as string of comma-separated values.
* This method should be used when configuring jetty-maven-plugin.
*
* @param csvResources the comma-separated string containing
* @param resources the comma-separated string containing
* one or more resource strings.
* @throws IOException if unable resource declared is not valid
* @see Resource#fromList(String, boolean)
*/
public void setResourcesAsCSV(String csvResources)
public void setResources(String resources) throws IOException
{
if (csvResources == null)
if (StringUtil.isBlank(resources))
{
throw new IllegalArgumentException("CSV String is null");
throw new IllegalArgumentException("String is blank");
}
StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
int len = tokenizer.countTokens();
if (len == 0)
List<Resource> list = Resource.fromList(resources, false);
if (list.isEmpty())
{
throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
" argument must be a string containing one or more comma-separated resource strings.");
throw new IllegalArgumentException("String contains no entries");
}
List<Resource> res = new ArrayList<>();
try
List<Resource> ret = new ArrayList<>();
for (Resource resource : list)
{
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);
ret.add(resource);
}
setResources(ret);
}
/**
@ -245,7 +234,7 @@ public class ResourceCollection extends Resource
if (path == null)
{
throw new MalformedURLException();
throw new MalformedURLException("null path");
}
if (path.length() == 0 || URIUtil.SLASH.equals(path))
@ -253,51 +242,32 @@ public class ResourceCollection extends Resource
return this;
}
Resource resource = null;
ArrayList<Resource> resources = null;
int i = 0;
for (; i < _resources.length; i++)
// Attempt a simple (single) Resource lookup that exists
for (Resource res : _resources)
{
resource = _resources[i].addPath(path);
if (resource.exists())
Resource r = res.addPath(path);
if (!r.isDirectory() && r.exists())
{
if (resource.isDirectory())
{
break;
}
return resource;
// Return simple (non-directory) Resource
return r;
}
}
for (i++; i < _resources.length; i++)
{
Resource r = _resources[i].addPath(path);
if (r.exists() && r.isDirectory())
if (resources == null)
{
if (resources == null)
{
resources = new ArrayList<>();
}
if (resource != null)
{
resources.add(resource);
resource = null;
}
resources.add(r);
resources = new ArrayList<>();
}
resources.add(r);
}
if (resource != null)
if (resources.size() == 1)
{
return resource;
return resources.get(0);
}
if (resources != null)
{
return new ResourceCollection(resources.toArray(new Resource[0]));
}
return null;
return new ResourceCollection(resources);
}
@Override
@ -437,9 +407,8 @@ public class ResourceCollection extends Resource
{
Collections.addAll(set, r.list());
}
String[] result = set.toArray(new String[0]);
Arrays.sort(result);
return result;
return (String[])set.stream().sorted().toArray();
}
@Override
@ -465,9 +434,10 @@ public class ResourceCollection extends Resource
{
assertResourcesSet();
for (int r = _resources.length; r-- > 0; )
// Copy in reverse order
for (int r = _resources.size(); r-- > 0; )
{
_resources[r].copyTo(destination);
_resources.get(r).copyTo(destination);
}
}
@ -477,12 +447,12 @@ public class ResourceCollection extends Resource
@Override
public String toString()
{
if (_resources == null || _resources.length == 0)
if (_resources.isEmpty())
{
return "[]";
}
return String.valueOf(Arrays.asList(_resources));
return String.valueOf(_resources);
}
@Override
@ -494,7 +464,7 @@ public class ResourceCollection extends Resource
private void assertResourcesSet()
{
if (_resources == null || _resources.length == 0)
if (_resources == null || _resources.isEmpty())
{
throw new IllegalStateException("*resources* not set.");
}

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, that might not actually exist (yet).
* @throws IOException if unable to create Resource
*/
Resource getResource(String path);
Resource getResource(String path) throws IOException;
}

View File

@ -27,6 +27,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.ReadableByteChannel;
import java.util.Objects;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.AutoLock;
@ -287,10 +288,9 @@ public class URLResource extends Resource
*/
@Override
public Resource addPath(String path)
throws IOException, MalformedURLException
throws IOException
{
if (path == null)
return null;
Objects.requireNonNull(path, "Path may not be null");
path = URIUtil.canonicalPath(path);

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);
}
@ -152,7 +152,7 @@ public class ResourceCollectionTest
assertThrows(IllegalStateException.class, () -> coll.setResources(new Resource[]{null, null, null}));
// Ensure not modified.
assertThat(coll.getResources().length, is(1));
assertThat(coll.getResources().size(), is(1));
}
private void assertThrowIllegalStateException(ResourceCollection 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("Error resolving file {}", entry, 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;
@ -87,7 +88,6 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
*/
public interface Context extends ClassVisibilityChecker
{
/**
* Convert a URL or path to a Resource.
* The default implementation
@ -112,7 +112,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
*/
boolean isParentLoaderPriority();
String getExtraClasspath();
List<Resource> getExtraClasspath();
boolean isServerResource(String name, URL parentUrl);
@ -185,7 +185,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 +193,12 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
if (context.getExtraClasspath() != null)
addClassPath(context.getExtraClasspath());
{
for (Resource resource : context.getExtraClasspath())
{
addClassPath(resource);
}
}
}
/**
@ -235,7 +240,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 +272,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

@ -31,6 +31,7 @@ import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -204,7 +205,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;
@ -1248,17 +1249,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;
}
@ -1450,11 +1463,12 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
// Should we go to the original war?
if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR())
{
Resource[] resources = ((ResourceCollection)resource).getResources();
for (int i = resources.length; i-- > 0; )
List<Resource> resources = ((ResourceCollection)resource).getResources();
for (int i = resources.size(); i-- > 0; )
{
if (resources[i].getName().startsWith("jar:file"))
return resources[i].getURI().toURL();
Resource r = resources.get(i);
if (r.getName().startsWith("jar:file"))
return r.getURI().toURL();
}
}