Skip to content

Commit

Permalink
Issue #5129 - Simplify Resource reference list behavior
Browse files Browse the repository at this point in the history
+ Introduce new Resource.fromReferences to help with
  parsing delimited resource reference lists.

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
  • Loading branch information
joakime committed Aug 10, 2020
1 parent 55f4e4f commit e809898
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 119 deletions.
Expand Up @@ -32,11 +32,13 @@
import java.nio.file.Paths;
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;
Expand Down Expand Up @@ -301,7 +303,6 @@ public static boolean isContainedIn(Resource r, Resource containingResource) thr
return r.isContainedIn(containingResource);
}


//@checkstyle-disable-check : NoFinalizer
@Override
protected void finalize()
Expand Down Expand Up @@ -995,4 +996,118 @@ public static URL toURL(File file) throws MalformedURLException
{
return file.toURI().toURL();
}

/**
* Factory to create new Resource instance from a reference.
*/
public interface Factory
{
/**
* Create a new Resource from the factory's point of view.
* <p>
* This is different then {@link ResourceFactory} in that
* it must return a {@link Resource} or throw an Exception,
* never null.
* </p>
*
* @param reference the string reference.
* @return the Resource instance
* @throws IOException if unable to create a Resource reference
*/
Resource newResource(String reference) throws IOException;
}

private static class DefaultFactory implements Factory
{
public static final Factory INSTANCE = new DefaultFactory();

@Override
public Resource newResource(String reference) throws IOException
{
return Resource.newResource(reference);
}
}

/**
* 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.
* @return the list of resources parsed from input string.
*/
public static List<Resource> fromReferences(String delimitedReferences) throws IOException
{
return fromReferences(delimitedReferences, DefaultFactory.INSTANCE);
}

/**
* 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 resourceFactory the Resource.Factory used to create new Resource references
* @return the list of resources parsed from input string.
*/
public static List<Resource> fromReferences(String delimitedReferences, Resource.Factory resourceFactory) throws IOException
{
if (StringUtil.isBlank(delimitedReferences))
{
return Collections.emptyList();
}

List<Resource> resources = new ArrayList<>();

StringTokenizer tokenizer = new StringTokenizer(delimitedReferences, ",;");
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.newResource(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
{
resources.add(dirResource.addPath(entry));
}
catch (Exception ex)
{
LOG.warn(Log.EXCEPTION, ex);
}
}
}
}
}
else
{
// Simple reference, add as-is
resources.add(resourceFactory.newResource(token));
}
}

return resources;
}
}
Expand Up @@ -234,7 +234,23 @@ public void addClassPath(Resource resource)
}
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);
}
}
}

Expand All @@ -250,53 +266,9 @@ public void addClassPath(String classPath)
if (classPath == null)
return;

StringTokenizer tokenizer = new StringTokenizer(classPath, ",;");
while (tokenizer.hasMoreTokens())
for (Resource resource : Resource.fromReferences(classPath, _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);
}
}

Expand Down
Expand Up @@ -29,8 +29,8 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.NetworkConnector;
Expand Down Expand Up @@ -943,55 +943,10 @@ protected List<Resource> findExtraClasspathJars(WebAppContext context)
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[] files = dirResource.list();
if (files != null)
{
Arrays.sort(files);
for (String file : files)
{
try
{
Resource fileResource = dirResource.addPath(file);
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 Resource.fromReferences(context.getExtraClasspath())
.stream()
.filter(WebInfConfiguration::isFileSupported)
.collect(Collectors.toList());
}

/**
Expand Down Expand Up @@ -1033,28 +988,13 @@ protected List<Resource> findExtraClasspathDirs(WebAppContext context)
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();
if (!isGlobReference(token))
{
Resource resource = context.newResource(token);
if (resource.exists() && resource.isDirectory())
dirResources.add(resource);
}
}

return dirResources;
}

private boolean isGlobReference(String token)
{
return token.endsWith("/*") || token.endsWith("\\*");
return Resource.fromReferences(context.getExtraClasspath())
.stream()
.filter(Resource::isDirectory)
.collect(Collectors.toList());
}

private boolean isFileSupported(Resource resource)
private static boolean isFileSupported(Resource resource)
{
String filenameLowercase = resource.getName().toLowerCase(Locale.ENGLISH);
int dot = filenameLowercase.lastIndexOf('.');
Expand Down
Expand Up @@ -133,7 +133,12 @@ public void testExtraClasspathGlob(String extraClasspathGlobReference) throws Ex
Path extLibsDir = MavenTestingUtils.getTestResourcePathDir("ext");
extLibsDir = extLibsDir.toAbsolutePath();
List<Path> expectedPaths = Files.list(extLibsDir)
.filter((path) -> path.toString().endsWith(".jar"))
.filter((path) ->
{
if (Files.isDirectory(path))
return true;
return Files.isRegularFile(path) && path.toString().endsWith(".jar");
})
.collect(Collectors.toList());
List<Path> actualPaths = new ArrayList<>();
for (URL url : webAppClassLoader.getURLs())
Expand Down

0 comments on commit e809898

Please sign in to comment.