ResourceCollection should not have a path (#8711)

ResourceCollection should not have a path
Nor name, nor filename unless all resources agree on it.
revert combine and related methods to return Resource and not explicitly a ResourceCollection, as if there is only 1, then a collection is not needed
cleanup ResourceCollection creation. Avoid sanity checks on resolved resources.
This commit is contained in:
Greg Wilkins 2022-11-01 15:54:50 +11:00 committed by GitHub
parent 4225054b62
commit 976ab3df8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 360 additions and 1256 deletions

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.ee9.quickstart;
package org.eclipse.jetty.util.resource;
import java.io.File;
import java.io.IOException;
@ -22,18 +22,15 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -165,81 +162,79 @@ public class AttributeNormalizer
}
}
private static Comparator<Attribute> attrComparator = new Comparator<Attribute>()
private static final Comparator<Attribute> attrComparator = (o1, o2) ->
{
@Override
public int compare(Attribute o1, Attribute o2)
if ((o1.value == null) && (o2.value != null))
{
if ((o1.value == null) && (o2.value != null))
{
return -1;
}
if ((o1.value != null) && (o2.value == null))
{
return 1;
}
if ((o1.value == null) && (o2.value == null))
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if (diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if (diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
return -1;
}
if ((o1.value != null) && (o2.value == null))
{
return 1;
}
if (o1.value == null)
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if (diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if (diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
};
private URI warURI;
private Map<String, Attribute> attributes = new HashMap<>();
private List<PathAttribute> paths = new ArrayList<>();
private List<URIAttribute> uris = new ArrayList<>();
private final List<PathAttribute> paths = new ArrayList<>();
private final List<URIAttribute> uris = new ArrayList<>();
public AttributeNormalizer(Resource baseResource)
{
if (baseResource == null)
throw new IllegalArgumentException("No base resource!");
warURI = toCanonicalURI(baseResource.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
addSystemProperty("jetty.base", 9);
addSystemProperty("jetty.home", 8);
addSystemProperty("user.home", 7);
addSystemProperty("user.dir", 6);
if (warURI.getScheme().equalsIgnoreCase("file"))
paths.add(new PathAttribute("WAR.path", toCanonicalPath(new File(warURI).toString()), 10));
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
Set<Path> rootPaths = new HashSet<>();
for (Resource r : baseResource)
{
if (r instanceof MountedPathResource mpr && rootPaths.contains(mpr.getContainerPath()))
return;
Collections.sort(paths, attrComparator);
Collections.sort(uris, attrComparator);
URI warURI = toCanonicalURI(r.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
Stream.concat(paths.stream(), uris.stream()).forEach(a -> attributes.put(a.key, a));
Path path = r.getPath();
if (path != null)
{
rootPaths.add(path);
paths.add(new PathAttribute("WAR.path", toCanonicalPath(path), 10));
}
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
}
paths.sort(attrComparator);
uris.sort(attrComparator);
if (LOG.isDebugEnabled())
{
for (Attribute attr : attributes.values())
{
LOG.debug(attr.toString());
}
}
Stream.concat(paths.stream(), uris.stream()).map(Object::toString).forEach(LOG::debug);
}
private void addSystemProperty(String key, int weight)
@ -352,7 +347,7 @@ public class AttributeNormalizer
return String.format("${%s}", a.key);
String s = uPath.substring(aPath.length());
if (s.length() > 0 && s.charAt(0) != '/')
if (s.charAt(0) != '/')
continue;
return String.format("${%s}%s", a.key, s);
@ -375,95 +370,79 @@ public class AttributeNormalizer
}
if (path.startsWith(a.path))
return String.format("${%s}%c%s", a.key, File.separatorChar, a.path.relativize(path).toString());
return String.format("${%s}%c%s", a.key, File.separatorChar, a.path.relativize(path));
}
return path.toString();
}
public String expand(String str)
{
return expand(str, new Stack<String>());
}
public String expand(String str, Stack<String> seenStack)
{
if (str == null)
{
return str;
}
if (str.indexOf("${") < 0)
if (!str.contains("${"))
{
// Contains no potential expressions.
return str;
}
Matcher mat = __propertyPattern.matcher(str);
StringBuilder expanded = new StringBuilder();
int offset = 0;
String property;
String value;
while (mat.find(offset))
if (mat.find(0))
{
property = mat.group(1);
// Loop detection
if (seenStack.contains(property))
{
StringBuilder err = new StringBuilder();
err.append("Property expansion loop detected: ");
int idx = seenStack.lastIndexOf(property);
for (int i = idx; i < seenStack.size(); i++)
{
err.append(seenStack.get(i));
err.append(" -> ");
}
err.append(property);
throw new RuntimeException(err.toString());
}
seenStack.push(property);
// find property name
expanded.append(str.subSequence(offset, mat.start()));
// get property value
value = getString(property);
if (value == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to expand: {}", property);
expanded.append(mat.group());
}
else
{
// recursively expand
value = expand(value, seenStack);
expanded.append(value);
}
// update offset
offset = mat.end();
String prefix = str.substring(0, mat.start());
String property = mat.group(1);
String suffix = str.substring(mat.end());
str = expand(prefix, property, suffix);
}
// leftover
expanded.append(str.substring(offset));
return StringUtil.replace(expanded.toString(), "$$", "$");
return StringUtil.replace(str, "$$", "$");
}
private String getString(String property)
private String expand(String prefix, String property, String suffix)
{
if (property == null)
{
return null;
for (URIAttribute attr : uris)
{
if (property.equals(attr.key))
{
try
{
String uri = prefix + attr.value + suffix;
Resource resource = ResourceFactory.root().newResource(uri);
if (resource.exists())
return uri;
}
catch (Exception ex)
{
if (LOG.isDebugEnabled())
LOG.trace("ignored", ex);
}
}
}
Attribute a = attributes.get(property);
if (a != null)
return a.value;
for (PathAttribute attr : paths)
{
if (property.equals(attr.key))
{
String path = prefix + attr.value + suffix;
if (Files.exists(Path.of(path)))
return path;
}
}
// Use system properties next
return System.getProperty(property);
String system = System.getProperty(property);
if (system != null)
return prefix + system + suffix;
String unexpanded = prefix + "${" + property + "}" + suffix;
LOG.warn("Cannot expand: {}", unexpanded);
return unexpanded;
}
}

View File

@ -13,11 +13,8 @@
package org.eclipse.jetty.util.resource;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
@ -30,32 +27,34 @@ import java.util.stream.Collectors;
import org.eclipse.jetty.util.URIUtil;
/**
* A collection of Resources.
* Allows webapps to have multiple sources.
* The first resource in the collection is the main resource.
* If a resource is not found in the main resource, it looks it up in
* the order the provided in the constructors
* Multiple resource directories presented as a single Resource.
*/
public class ResourceCollection extends Resource
public class CombinedResource extends Resource
{
private final List<Resource> _resources;
/**
* Instantiates a new resource collection.
*
* @param resources the resources to be added to collection
* <p>Make a Resource containing a combination of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources or a single resource if only 1 is passed, or null if none are passed
* @see CombinedResource
*/
ResourceCollection(List<Resource> resources)
static Resource combine(List<Resource> resources)
{
List<Resource> res = new ArrayList<>();
gatherUniqueFlatResourceList(res, resources);
_resources = Collections.unmodifiableList(res);
resources = CombinedResource.gatherUniqueFlatResourceList(resources);
if (resources == null || resources.isEmpty())
return null;
if (resources.size() == 1)
return resources.get(0);
return new CombinedResource(resources);
}
private static void gatherUniqueFlatResourceList(List<Resource> unique, List<Resource> resources)
static List<Resource> gatherUniqueFlatResourceList(List<Resource> resources)
{
if (resources == null || resources.isEmpty())
throw new IllegalArgumentException("Empty Resource collection");
return null;
List<Resource> unique = new ArrayList<>(resources.size());
for (Resource r : resources)
{
@ -64,9 +63,9 @@ public class ResourceCollection extends Resource
throw new IllegalArgumentException("Null Resource entry encountered");
}
if (r instanceof ResourceCollection resourceCollection)
if (r instanceof CombinedResource resourceCollection)
{
gatherUniqueFlatResourceList(unique, resourceCollection.getResources());
unique.addAll(gatherUniqueFlatResourceList(resourceCollection.getResources()));
}
else
{
@ -89,6 +88,19 @@ public class ResourceCollection extends Resource
unique.add(r);
}
}
return unique;
}
private final List<Resource> _resources;
/**
* Instantiates a new resource collection.
*
* @param resources the resources to be added to collection
*/
CombinedResource(List<Resource> resources)
{
_resources = Collections.unmodifiableList(resources);
}
/**
@ -127,127 +139,67 @@ public class ResourceCollection extends Resource
ArrayList<Resource> resources = null;
// Attempt a simple (single) Resource lookup that exists
Resource addedResource = null;
Resource resolved = null;
for (Resource res : _resources)
{
addedResource = res.resolve(subUriPath);
if (Resources.missing(addedResource))
resolved = res.resolve(subUriPath);
if (Resources.missing(resolved))
continue; // skip, doesn't exist
if (!addedResource.isDirectory())
return addedResource; // Return simple (non-directory) Resource
if (!resolved.isDirectory())
return resolved; // Return simple (non-directory) Resource
if (resources == null)
resources = new ArrayList<>();
resources.add(addedResource);
resources.add(resolved);
}
if (resources == null)
return addedResource; // This will not exist
return resolved; // This will not exist
if (resources.size() == 1)
return resources.get(0);
return new ResourceCollection(resources);
return new CombinedResource(resources);
}
@Override
public boolean exists()
{
for (Resource r : _resources)
{
if (r.exists())
{
return true;
}
}
return false;
return _resources.stream().anyMatch(Resource::exists);
}
@Override
public Path getPath()
{
for (Resource r : _resources)
{
Path p = r.getPath();
if (p != null)
return p;
}
return null;
}
@Override
public InputStream newInputStream() throws IOException
{
for (Resource r : _resources)
{
if (!r.exists())
{
// Skip, cannot open anyway
continue;
}
InputStream is = r.newInputStream();
if (is != null)
{
return is;
}
}
throw new FileNotFoundException("Resource does not exist");
}
@Override
public ReadableByteChannel newReadableByteChannel() throws IOException
{
for (Resource r : _resources)
{
ReadableByteChannel channel = r.newReadableByteChannel();
if (channel != null)
{
return channel;
}
}
return null;
}
@Override
public String getName()
{
for (Resource r : _resources)
{
String name = r.getName();
if (name != null)
{
return name;
}
}
return null;
}
@Override
public String getFileName()
{
String filename = null;
// return a non-null filename only if all resources agree on the same name.
for (Resource r : _resources)
{
String filename = r.getFileName();
if (filename != null)
{
return filename;
}
String fn = r.getFileName();
if (fn == null)
return null;
if (filename == null)
filename = fn;
else if (!filename.equals(fn))
return null;
}
return null;
return filename;
}
@Override
public URI getURI()
{
for (Resource r : _resources)
{
URI uri = r.getURI();
if (uri != null)
{
return uri;
}
}
return null;
}
@ -326,7 +278,7 @@ public class ResourceCollection extends Resource
return true;
if (o == null || getClass() != o.getClass())
return false;
ResourceCollection other = (ResourceCollection)o;
CombinedResource other = (CombinedResource)o;
return Objects.equals(_resources, other._resources);
}
@ -343,7 +295,7 @@ public class ResourceCollection extends Resource
public String toString()
{
return _resources.stream()
.map(Resource::getName)
.map(Resource::toString)
.collect(Collectors.joining(", ", "[", "]"));
}

View File

@ -113,11 +113,8 @@ public abstract class Resource implements Iterable<Resource>
public abstract boolean isContainedIn(Resource r);
/**
* Return an Iterator of all Resource's referenced in this Resource.
*
* <p>
* This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator.
* </p>
* <p>Return an Iterator of all Resource's referenced in this Resource.</p>
* <p>This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator of this resource.</p>
*
* @return the iterator of Resources.
*/
@ -198,22 +195,28 @@ public abstract class Resource implements Iterable<Resource>
/**
* Creates a new input stream to the resource.
*
* @return an input stream to the resource
* @throws IOException if unable to open the input stream
* @return an input stream to the resource or null if one is not available.
* @throws IOException if there is a problem opening the input stream
*/
public InputStream newInputStream() throws IOException
{
return Files.newInputStream(getPath(), StandardOpenOption.READ);
Path path = getPath();
if (path == null)
return null;
return Files.newInputStream(path, StandardOpenOption.READ);
}
/**
* Readable ByteChannel for the resource.
*
* @return an readable bytechannel to the resource or null if one is not available.
* @return a readable {@link java.nio.channels.ByteChannel} to the resource or null if one is not available.
* @throws IOException if unable to open the readable bytechannel for the resource.
*/
public ReadableByteChannel newReadableByteChannel() throws IOException
{
Path path = getPath();
if (path == null)
return null;
return Files.newByteChannel(getPath(), StandardOpenOption.READ);
}

View File

@ -35,27 +35,23 @@ public interface ResourceFactory
/**
* <p>Make a Resource containing a collection of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources.
* @see ResourceCollection
* @return A Resource of multiple resources or a single resource if only 1 is passed, or null if none are passed
* @see CombinedResource
*/
static ResourceCollection combine(List<Resource> resources)
static Resource combine(List<Resource> resources)
{
if (resources == null || resources.isEmpty())
throw new IllegalArgumentException("No resources");
return new ResourceCollection(resources);
return CombinedResource.combine(resources);
}
/**
* <p>Make a Resource containing a collection of other resources</p>
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
* @return A Resource of multiple resources.
* @see ResourceCollection
* @see CombinedResource
*/
static ResourceCollection combine(Resource... resources)
static Resource combine(Resource... resources)
{
if (resources == null || resources.length == 0)
throw new IllegalArgumentException("No resources");
return new ResourceCollection(List.of(resources));
return CombinedResource.combine(List.of(resources));
}
/**
@ -203,12 +199,12 @@ public interface ResourceFactory
}
/**
* Construct a ResourceCollection from a list of URIs
* Construct a possible {@link CombinedResource} from a list of URIs
*
* @param uris the URIs
* @return the Resource for the provided path
*/
default ResourceCollection newResource(List<URI> uris)
default Resource newResource(List<URI> uris)
{
if ((uris == null) || (uris.isEmpty()))
throw new IllegalArgumentException("List of URIs is invalid");

View File

@ -11,8 +11,9 @@
// ========================================================================
//
package org.eclipse.jetty.ee9.quickstart;
package org.eclipse.jetty.util.resource;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
@ -26,11 +27,9 @@ import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -43,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class AttributeNormalizerTest
{
public static Stream<Arguments> scenarios()
public static Stream<Arguments> scenarios() throws IOException
{
final List<Arguments> data = new ArrayList<>();
final String arch = String.format("%s/%s", System.getProperty("os.name"), System.getProperty("os.arch"));
@ -55,18 +54,22 @@ public class AttributeNormalizerTest
// ------
title = "Typical Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro/demo.base");
war = asTargetPath(title, "jetty-distro/demo.base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
jettyHome = asTargetPath(title, "jetty-typical");
jettyBase = asTargetPath(title, "jetty-typical/demo.base");
war = asTargetPath(title, "jetty-typical/demo.base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
title = "Old Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro");
war = asTargetPath(title, "jetty-distro/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
jettyHome = asTargetPath(title, "jetty-old");
jettyBase = asTargetPath(title, "jetty-old");
war = asTargetPath(title, "jetty-old/webapps/FOO");
if (!Files.exists(war.resolve("index.html")))
{
Files.createFile(war.resolve("index.html"));
Files.createFile(war.resolve("favicon.ico"));
}
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
// This puts the jetty.home inside the jetty.base
@ -74,8 +77,7 @@ public class AttributeNormalizerTest
jettyHome = asTargetPath(title, "app/dist");
jettyBase = asTargetPath(title, "app");
war = asTargetPath(title, "app/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
// ------
// This tests a path scenario often seen on various automatic deployments tooling
@ -84,8 +86,16 @@ public class AttributeNormalizerTest
jettyHome = asTargetPath(title, "app%2Fnasty/dist");
jettyBase = asTargetPath(title, "app%2Fnasty/base");
war = asTargetPath(title, "app%2Fnasty/base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, resourceFactory.newResource(war))));
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
// ------
title = "ResourceCollection Setup";
jettyHome = asTargetPath(title, "jetty-collection");
jettyBase = asTargetPath(title, "jetty-collection/demo.base");
Path warA = asTargetPath(title, "jetty-collection/demo.base/webapps/WarA");
Path warB = asTargetPath(title, "jetty-collection/demo.base/webapps/WarB");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase,
ResourceFactory.combine(resourceFactory.newResource(warA), resourceFactory.newResource(warB)))));
return data.stream();
}
@ -166,7 +176,9 @@ public class AttributeNormalizerTest
public void testNormalizeWarAsString(final Scenario scenario)
{
// Normalize WAR as String path
assertNormalize(scenario, scenario.war.toString(), scenario.war.toString());
Path path = scenario.war.getPath();
Assumptions.assumeTrue(path != null);
assertNormalize(scenario, path.toString(), path.toString());
}
@ParameterizedTest
@ -259,12 +271,22 @@ public class AttributeNormalizerTest
assertExpandPath(scenario, "${jetty.home}", scenario.jettyHome.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandWebInfAsURI(final Scenario scenario)
{
// Expand
assertExpandURI(scenario, "${WAR.uri}/WEB-INF/web.xml", scenario.webXml.toUri());
assertExpandURI(scenario, "${WAR.uri}/WEB-INF/test.tld", scenario.testTld.toUri());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarAsURI(final Scenario scenario)
{
// Normalize WAR as URI
URI testWarURI = scenario.war.toUri();
URI testWarURI = scenario.war.getURI();
Assumptions.assumeTrue(testWarURI != null);
assertNormalize(scenario, testWarURI, "${WAR.uri}");
}
@ -273,7 +295,7 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsPath(final Scenario scenario)
{
// Normalize WAR deep path as File
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep, "${WAR.path}" + FS.separators("/deep/ref"));
}
@ -282,7 +304,7 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsString(final Scenario scenario)
{
// Normalize WAR deep path as String
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep.toString(), testWarDeep.toString());
}
@ -291,30 +313,22 @@ public class AttributeNormalizerTest
public void testNormalizeWarDeepAsURI(final Scenario scenario)
{
// Normalize WAR deep path as URI
Path testWarDeep = scenario.war.resolve("deep/ref");
Path testWarDeep = scenario.war.resolve("deep/ref").getPath();
assertNormalize(scenario, testWarDeep.toUri(), "${WAR.uri}/deep/ref");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandWarDeep(final Scenario scenario)
{
// Expand WAR deep path
Path testWarDeep = scenario.war.resolve("deep/ref");
URI uri = URI.create("jar:" + testWarDeep.toUri().toASCIIString() + "!/other/file");
assertExpandURI(scenario, "jar:${WAR.uri}/deep/ref!/other/file", uri);
}
public static class Scenario
{
private final Path jettyHome;
private final Path jettyBase;
private final Path war;
private final Resource war;
private Path webXml;
private Path testTld;
private final String arch;
private final String title;
private final AttributeNormalizer normalizer;
public Scenario(String arch, String title, Path jettyHome, Path jettyBase, Path war)
public Scenario(String arch, String title, Path jettyHome, Path jettyBase, Resource war)
{
this.arch = arch;
this.title = title;
@ -326,15 +340,48 @@ public class AttributeNormalizerTest
assertTrue(Files.exists(this.jettyHome));
assertTrue(Files.exists(this.jettyBase));
assertTrue(Files.exists(this.war));
assertTrue(war.exists());
// Set some System Properties that AttributeNormalizer expects
System.setProperty("jetty.home", jettyHome.toString());
System.setProperty("jetty.base", jettyBase.toString());
for (Resource w : war)
{
try
{
Path webinf = w.getPath().resolve("WEB-INF");
if (!Files.exists(webinf))
Files.createDirectory(webinf);
Path deep = w.getPath().resolve("deep");
if (!Files.exists(deep))
Files.createDirectory(deep);
Path ref = deep.resolve("ref");
if (!Files.exists(ref))
Files.createFile(ref);
if (w.getFileName().equals("FOO") || w.getFileName().equals("WarA"))
{
webXml = webinf.resolve("web.xml");
if (!Files.exists(webXml))
Files.createFile(webXml);
}
if (w.getFileName().equals("FOO") || w.getFileName().equals("WarB"))
{
testTld = webinf.resolve("test.tld");
if (!Files.exists(testTld))
Files.createFile(testTld);
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
// Setup normalizer
Resource webresource = resourceFactory.newResource(war);
this.normalizer = new AttributeNormalizer(webresource);
this.normalizer = new AttributeNormalizer(war);
}
@Override

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.ee9.quickstart;
package org.eclipse.jetty.util.resource;
import java.net.URI;
import java.util.ArrayList;

View File

@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class ResourceCollectionTest
public class CombinedResourceTest
{
private final ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
public WorkDir workDir;
@ -78,7 +78,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -127,7 +127,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -135,8 +135,8 @@ public class ResourceCollectionTest
// This should return a ResourceCollection with 3 `/dir/` sub-directories.
Resource r = rc.resolve("dir");
assertTrue(r instanceof ResourceCollection);
rc = (ResourceCollection)r;
assertTrue(r instanceof CombinedResource);
rc = (CombinedResource)r;
assertEquals(getContent(rc, "1.txt"), "1 - one (in dir)");
assertEquals(getContent(rc, "2.txt"), "2 - two (in dir)");
assertEquals(getContent(rc, "3.txt"), "3 - three (in dir)");
@ -149,7 +149,7 @@ public class ResourceCollectionTest
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
ResourceCollection rc = ResourceFactory.combine(
Resource rc = ResourceFactory.combine(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
resourceFactory.newResource(three)
@ -175,7 +175,7 @@ public class ResourceCollectionTest
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
Path twoDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two/dir");
ResourceCollection rc1 = ResourceFactory.combine(
Resource rc1 = ResourceFactory.combine(
List.of(
resourceFactory.newResource(one),
resourceFactory.newResource(two),
@ -183,7 +183,7 @@ public class ResourceCollectionTest
)
);
ResourceCollection rc2 = ResourceFactory.combine(
Resource rc2 = ResourceFactory.combine(
List.of(
// the original ResourceCollection
rc1,
@ -202,9 +202,11 @@ public class ResourceCollectionTest
};
List<URI> actual = new ArrayList<>();
for (Resource res: rc2.getResources())
assertThat(rc2, instanceOf(CombinedResource.class));
if (rc2 instanceof CombinedResource combinedResource)
{
actual.add(res.getURI());
for (Resource res : combinedResource.getResources())
actual.add(res.getURI());
}
assertThat(actual, contains(expected));
}
@ -307,7 +309,7 @@ public class ResourceCollectionTest
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there
// will be necessary things to mount
ResourceCollection rc = resourceFactory.newResource(uris);
Resource rc = resourceFactory.newResource(uris);
assertThat(getContent(rc, "test.txt"), is("Test"));
}
@ -333,7 +335,7 @@ public class ResourceCollectionTest
// Since this is user space, we cannot know ahead of time what
// this list contains, so we mount because we assume there
// will be necessary things to mount
ResourceCollection rc = resourceFactory.newResource(uris);
Resource rc = resourceFactory.newResource(uris);
assertThat(getContent(rc, "test.txt"), is("Test inside lib-foo.jar"));
assertThat(getContent(rc, "testZed.txt"), is("TestZed inside lib-zed.jar"));
}

View File

@ -17,8 +17,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.ee10.webapp.Configuration;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,9 +47,9 @@ public class MavenQuickStartConfiguration extends QuickStartConfiguration
//Iterate over all of the resource bases and ignore any that were original bases, just
//deleting the overlays
Resource res = context.getBaseResource();
if (res instanceof ResourceCollection)
if (res instanceof CombinedResource)
{
for (Resource r : ((ResourceCollection)res).getResources())
for (Resource r : ((CombinedResource)res).getResources())
{
if (originalBaseStr.contains(r.toString()))
continue;

View File

@ -39,8 +39,8 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -218,7 +218,7 @@ public class MavenWebAppContext extends WebAppContext
* configuration
*
* @param resourceBases Array of resources strings to set as a
* {@link ResourceCollection}.
* {@link CombinedResource}.
*/
public void setResourceBases(String[] resourceBases)
{

View File

@ -30,8 +30,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
@ -106,8 +106,8 @@ public class WebAppPropertyConverter
//send over the calculated resource bases that includes unpacked overlays
Resource baseResource = webApp.getBaseResource();
if (baseResource instanceof ResourceCollection)
props.put(BASE_DIRS, toCSV(((ResourceCollection)webApp.getBaseResource()).getResources()));
if (baseResource instanceof CombinedResource)
props.put(BASE_DIRS, toCSV(((CombinedResource)webApp.getBaseResource()).getResources()));
else if (baseResource instanceof Resource)
props.put(BASE_DIRS, webApp.getBaseResource().toString());

View File

@ -16,17 +16,17 @@ package org.eclipse.jetty.ee10.maven.plugin;
import java.io.File;
import java.io.FileInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -152,10 +152,13 @@ public class TestWebAppPropertyConverter
assertEquals(true, webApp.isPersistTempDirectory());
assertEquals(war.getAbsolutePath(), webApp.getWar());
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
assertThat(webApp.getBaseResource(), instanceOf(ResourceCollection.class));
assertThat(webApp.getBaseResource(), instanceOf(CombinedResource.class));
ResourceCollection resourceCollection = (ResourceCollection)webApp.getBaseResource();
List<URI> actual = resourceCollection.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
Resource combinedResource = webApp.getBaseResource();
List<URI> actual = new ArrayList<>();
for (Resource r : combinedResource)
if (r != null)
actual.add(r.getURI());
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
assertThat(actual, containsInAnyOrder(expected));
}

View File

@ -1,469 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee10.quickstart;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Normalize Attribute to String.
* <p>
* Replaces and expands:
* <ul>
* <li>${WAR}</li>
* <li>${WAR.path}</li>
* <li>${WAR.uri}</li>
* <li>${jetty.base}</li>
* <li>${jetty.base.uri}</li>
* <li>${jetty.home}</li>
* <li>${jetty.home.uri}</li>
* <li>${user.home}</li>
* <li>${user.home.uri}</li>
* <li>${user.dir}</li>
* <li>${user.dir.uri}</li>
* </ul>
*/
public class AttributeNormalizer
{
private static final Logger LOG = LoggerFactory.getLogger(AttributeNormalizer.class);
private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}");
private static class Attribute
{
final String key;
final String value;
final int weight;
public Attribute(String key, String value, int weight)
{
this.key = key;
this.value = value;
this.weight = weight;
}
}
public static URI toCanonicalURI(URI uri)
{
uri = uri.normalize();
String path = uri.getPath();
if (path != null && path.length() > 1 && path.endsWith("/"))
{
try
{
String ascii = uri.toASCIIString();
uri = new URI(ascii.substring(0, ascii.length() - 1));
}
catch (URISyntaxException e)
{
throw new IllegalArgumentException(e);
}
}
return uri;
}
public static String toCanonicalURI(String uri)
{
if (uri != null && uri.length() > 1 && uri.endsWith("/"))
{
return uri.substring(0, uri.length() - 1);
}
return uri;
}
public static Path toCanonicalPath(String path)
{
if (path == null)
return null;
if (path.length() > 1 && path.endsWith("/"))
path = path.substring(0, path.length() - 1);
return toCanonicalPath(FileSystems.getDefault().getPath(path));
}
private static Path toCanonicalPath(Path path)
{
if (path == null)
{
return null;
}
if (Files.exists(path))
{
try
{
return path.toRealPath();
}
catch (IOException e)
{
throw new IllegalArgumentException(e);
}
}
return path.toAbsolutePath();
}
private static class PathAttribute extends Attribute
{
public final Path path;
public PathAttribute(String key, Path path, int weight)
{
super(key, path.toString(), weight);
this.path = path;
}
@Override
public String toString()
{
return String.format("PathAttribute[%s=>%s]", key, path);
}
}
private static class URIAttribute extends Attribute
{
public final URI uri;
public URIAttribute(String key, URI uri, int weight)
{
super(key, toCanonicalURI(uri.toASCIIString()), weight);
this.uri = toCanonicalURI(uri);
}
@Override
public String toString()
{
return String.format("URIAttribute[%s=>%s]", key, uri);
}
}
private static Comparator<Attribute> attrComparator = new Comparator<Attribute>()
{
@Override
public int compare(Attribute o1, Attribute o2)
{
if ((o1.value == null) && (o2.value != null))
{
return -1;
}
if ((o1.value != null) && (o2.value == null))
{
return 1;
}
if ((o1.value == null) && (o2.value == null))
{
return 0;
}
// Different lengths?
int diff = o2.value.length() - o1.value.length();
if (diff != 0)
{
return diff;
}
// Different names?
diff = o2.value.compareTo(o1.value);
if (diff != 0)
{
return diff;
}
// The paths are the same, base now on weight
return o2.weight - o1.weight;
}
};
private URI warURI;
private Map<String, Attribute> attributes = new HashMap<>();
private List<PathAttribute> paths = new ArrayList<>();
private List<URIAttribute> uris = new ArrayList<>();
public AttributeNormalizer(Resource baseResource)
{
if (baseResource == null)
throw new IllegalArgumentException("No base resource!");
warURI = toCanonicalURI(baseResource.getURI());
if (!warURI.isAbsolute())
throw new IllegalArgumentException("WAR URI is not absolute: " + warURI);
addSystemProperty("jetty.base", 9);
addSystemProperty("jetty.home", 8);
addSystemProperty("user.home", 7);
addSystemProperty("user.dir", 6);
if (warURI.getScheme().equalsIgnoreCase("file"))
paths.add(new PathAttribute("WAR.path", toCanonicalPath(new File(warURI).toString()), 10));
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
Collections.sort(paths, attrComparator);
Collections.sort(uris, attrComparator);
Stream.concat(paths.stream(), uris.stream()).forEach(a -> attributes.put(a.key, a));
if (LOG.isDebugEnabled())
{
for (Attribute attr : attributes.values())
{
LOG.debug(attr.toString());
}
}
}
private void addSystemProperty(String key, int weight)
{
String value = System.getProperty(key);
if (value != null)
{
Path path = toCanonicalPath(value);
paths.add(new PathAttribute(key, path, weight));
uris.add(new URIAttribute(key + ".uri", path.toUri(), weight));
}
}
/**
* Normalize a URI, URL, or File reference by replacing known attributes with ${key} attributes.
*
* @param o the object to normalize into a string
* @return the string representation of the object, with expansion keys.
*/
public String normalize(Object o)
{
try
{
// Find a URI
URI uri = null;
Path path = null;
if (o instanceof URI)
uri = toCanonicalURI(((URI)o));
else if (o instanceof Resource)
uri = toCanonicalURI(((Resource)o).getURI());
else if (o instanceof URL)
uri = toCanonicalURI(((URL)o).toURI());
else if (o instanceof File)
path = ((File)o).getAbsoluteFile().getCanonicalFile().toPath();
else if (o instanceof Path)
path = (Path)o;
else
{
String s = o.toString();
try
{
uri = new URI(s);
if (uri.getScheme() == null)
{
// Unknown scheme? not relevant to normalize
return s;
}
}
catch (URISyntaxException e)
{
// This path occurs for many reasons, but most common is when this
// is executed on MS Windows, on a string like "D:\jetty"
// and the new URI() fails for
// java.net.URISyntaxException: Illegal character in opaque part at index 2: D:\jetty
return s;
}
}
if (uri != null)
{
if ("jar".equalsIgnoreCase(uri.getScheme()))
{
String raw = uri.getRawSchemeSpecificPart();
int bang = raw.indexOf("!/");
String normal = normalize(raw.substring(0, bang));
String suffix = raw.substring(bang);
return "jar:" + normal + suffix;
}
else
{
if (uri.isAbsolute())
{
return normalizeUri(uri);
}
}
}
else if (path != null)
return normalizePath(path);
}
catch (Exception e)
{
LOG.warn("Failed to normalize {}", o, e);
}
return String.valueOf(o);
}
protected String normalizeUri(URI uri)
{
for (URIAttribute a : uris)
{
if (uri.compareTo(a.uri) == 0)
return String.format("${%s}", a.key);
if (!a.uri.getScheme().equalsIgnoreCase(uri.getScheme()))
continue;
if (a.uri.getHost() == null && uri.getHost() != null)
continue;
if (a.uri.getHost() != null && !a.uri.getHost().equals(uri.getHost()))
continue;
String aPath = a.uri.getPath();
String uPath = uri.getPath();
if (aPath.equals(uPath))
return a.value;
if (!uPath.startsWith(aPath))
continue;
if (uPath.length() == aPath.length())
return String.format("${%s}", a.key);
String s = uPath.substring(aPath.length());
if (s.length() > 0 && s.charAt(0) != '/')
continue;
return String.format("${%s}%s", a.key, s);
}
return uri.toASCIIString();
}
protected String normalizePath(Path path)
{
for (PathAttribute a : paths)
{
try
{
if (path.equals(a.path) || Files.isSameFile(path, a.path))
return String.format("${%s}", a.key);
}
catch (IOException ignore)
{
LOG.trace("IGNORED", ignore);
}
if (path.startsWith(a.path))
return String.format("${%s}%c%s", a.key, File.separatorChar, a.path.relativize(path).toString());
}
return path.toString();
}
public String expand(String str)
{
return expand(str, new Stack<String>());
}
public String expand(String str, Stack<String> seenStack)
{
if (str == null)
{
return str;
}
if (str.indexOf("${") < 0)
{
// Contains no potential expressions.
return str;
}
Matcher mat = __propertyPattern.matcher(str);
StringBuilder expanded = new StringBuilder();
int offset = 0;
String property;
String value;
while (mat.find(offset))
{
property = mat.group(1);
// Loop detection
if (seenStack.contains(property))
{
StringBuilder err = new StringBuilder();
err.append("Property expansion loop detected: ");
int idx = seenStack.lastIndexOf(property);
for (int i = idx; i < seenStack.size(); i++)
{
err.append(seenStack.get(i));
err.append(" -> ");
}
err.append(property);
throw new RuntimeException(err.toString());
}
seenStack.push(property);
// find property name
expanded.append(str.subSequence(offset, mat.start()));
// get property value
value = getString(property);
if (value == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to expand: {}", property);
expanded.append(mat.group());
}
else
{
// recursively expand
value = expand(value, seenStack);
expanded.append(value);
}
// update offset
offset = mat.end();
}
// leftover
expanded.append(str.substring(offset));
return StringUtil.replace(expanded.toString(), "$$", "$");
}
private String getString(String property)
{
if (property == null)
{
return null;
}
Attribute a = attributes.get(property);
if (a != null)
return a.value;
// Use system properties next
return System.getProperty(property);
}
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.AttributeNormalizer;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.ee10.webapp.WebInfConfiguration;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.AttributeNormalizer;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.xml.XmlAppendable;
import org.slf4j.Logger;

View File

@ -1,347 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee10.quickstart;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class AttributeNormalizerTest
{
public static Stream<Arguments> scenarios()
{
final List<Arguments> data = new ArrayList<>();
final String arch = String.format("%s/%s", System.getProperty("os.name"), System.getProperty("os.arch"));
String title;
Path jettyHome;
Path jettyBase;
Path war;
// ------
title = "Typical Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro/demo.base");
war = asTargetPath(title, "jetty-distro/demo.base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
// ------
title = "Old Setup";
jettyHome = asTargetPath(title, "jetty-distro");
jettyBase = asTargetPath(title, "jetty-distro");
war = asTargetPath(title, "jetty-distro/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
// ------
// This puts the jetty.home inside the jetty.base
title = "Overlap Setup";
jettyHome = asTargetPath(title, "app/dist");
jettyBase = asTargetPath(title, "app");
war = asTargetPath(title, "app/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
// ------
// This tests a path scenario often seen on various automatic deployments tooling
// such as Kubernetes, CircleCI, TravisCI, and Jenkins.
title = "Nasty Path Setup";
jettyHome = asTargetPath(title, "app%2Fnasty/dist");
jettyBase = asTargetPath(title, "app%2Fnasty/base");
war = asTargetPath(title, "app%2Fnasty/base/webapps/FOO");
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
return data.stream();
}
private static Path asTargetPath(String title, String subpath)
{
Path rootPath = MavenTestingUtils.getTargetTestingPath(title);
FS.ensureDirExists(rootPath);
Path path = rootPath.resolve(FS.separators(subpath));
FS.ensureDirExists(path);
return path;
}
private static final Map<String, String> originalEnv = new HashMap<>();
private static ResourceFactory.Closeable resourceFactory;
@BeforeAll
public static void rememberOriginalEnv()
{
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
resourceFactory = ResourceFactory.closeable();
System.getProperties().stringPropertyNames()
.forEach((name) -> originalEnv.put(name, System.getProperty(name)));
}
@AfterAll
public static void afterAll()
{
IO.close(resourceFactory);
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
}
@AfterEach
public void restoreOriginalEnv()
{
originalEnv.forEach(AttributeNormalizerTest::restoreSystemProperty);
}
private static void restoreSystemProperty(String key, String value)
{
if (value == null)
{
System.clearProperty(key);
}
else
{
System.setProperty(key, value);
}
}
private void assertNormalize(final Scenario scenario, Object o, String expected)
{
String result = scenario.normalizer.normalize(o);
assertThat("normalize((" + o.getClass().getSimpleName() + ") " + Objects.toString(o, "<null>") + ")",
result, is(expected));
}
private void assertExpandPath(final Scenario scenario, String line, String expected)
{
String result = scenario.normalizer.expand(line);
// Treat output as strings
assertThat("expand('" + line + "')", result, is(expected));
}
private void assertExpandURI(final Scenario scenario, String line, URI expected)
{
String result = scenario.normalizer.expand(line);
URI resultURI = URI.create(result);
assertThat("expand('" + line + "')", resultURI.getScheme(), is(expected.getScheme()));
assertThat("expand('" + line + "')", resultURI.getPath(), is(expected.getPath()));
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarAsString(final Scenario scenario)
{
// Normalize WAR as String path
assertNormalize(scenario, scenario.war.toString(), scenario.war.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyBaseAsFile(final Scenario scenario)
{
// Normalize jetty.base as File path
assertNormalize(scenario, scenario.jettyBase.toFile(), "${jetty.base}");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyHomeAsFile(final Scenario scenario)
{
// Normalize jetty.home as File path
String expected = scenario.jettyBase.equals(scenario.jettyHome) ? "${jetty.base}" : "${jetty.home}";
assertNormalize(scenario, scenario.jettyHome.toFile(), expected);
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyBaseAsPath(final Scenario scenario)
{
// Normalize jetty.base as File path
assertNormalize(scenario, scenario.jettyBase, "${jetty.base}");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyHomeAsPath(final Scenario scenario)
{
// Normalize jetty.home as File path
String expected = scenario.jettyBase.equals(scenario.jettyHome) ? "${jetty.base}" : "${jetty.home}";
assertNormalize(scenario, scenario.jettyHome, expected);
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyBaseAsURIWithAuthority(final Scenario scenario)
{
// Normalize jetty.base as URI path
// Path.toUri() typically includes an URI authority
assertNormalize(scenario, scenario.jettyBase.toUri(), "${jetty.base.uri}");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyBaseAsURIWithoutAuthority(final Scenario scenario)
{
// Normalize jetty.base as URI path
// File.toURI() typically DOES NOT include an URI authority
assertNormalize(scenario, scenario.jettyBase.toFile().toURI(), "${jetty.base.uri}");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyHomeAsURIWithAuthority(final Scenario scenario)
{
// Normalize jetty.home as URI path
String expected = scenario.jettyBase.equals(scenario.jettyHome) ? "${jetty.base.uri}" : "${jetty.home.uri}";
// Path.toUri() typically includes an URI authority
assertNormalize(scenario, scenario.jettyHome.toUri(), expected);
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeJettyHomeAsURIWithoutAuthority(final Scenario scenario)
{
// Normalize jetty.home as URI path
String expected = scenario.jettyBase.equals(scenario.jettyHome) ? "${jetty.base.uri}" : "${jetty.home.uri}";
// File.toURI() typically DOES NOT include an URI authority
assertNormalize(scenario, scenario.jettyHome.toFile().toURI(), expected);
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandJettyBase(final Scenario scenario)
{
// Expand jetty.base
assertExpandPath(scenario, "${jetty.base}", scenario.jettyBase.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandJettyHome(final Scenario scenario)
{
// Expand jetty.home
assertExpandPath(scenario, "${jetty.home}", scenario.jettyHome.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarAsURI(final Scenario scenario)
{
// Normalize WAR as URI
URI testWarURI = scenario.war.toUri();
assertNormalize(scenario, testWarURI, "${WAR.uri}");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarDeepAsPath(final Scenario scenario)
{
// Normalize WAR deep path as File
Path testWarDeep = scenario.war.resolve("deep/ref");
assertNormalize(scenario, testWarDeep, "${WAR.path}" + FS.separators("/deep/ref"));
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarDeepAsString(final Scenario scenario)
{
// Normalize WAR deep path as String
Path testWarDeep = scenario.war.resolve("deep/ref");
assertNormalize(scenario, testWarDeep.toString(), testWarDeep.toString());
}
@ParameterizedTest
@MethodSource("scenarios")
public void testNormalizeWarDeepAsURI(final Scenario scenario)
{
// Normalize WAR deep path as URI
Path testWarDeep = scenario.war.resolve("deep/ref");
assertNormalize(scenario, testWarDeep.toUri(), "${WAR.uri}/deep/ref");
}
@ParameterizedTest
@MethodSource("scenarios")
public void testExpandWarDeep(final Scenario scenario)
{
// Expand WAR deep path
Path testWarDeep = scenario.war.resolve("deep/ref");
URI uri = URI.create("jar:" + testWarDeep.toUri().toASCIIString() + "!/other/file");
assertExpandURI(scenario, "jar:${WAR.uri}/deep/ref!/other/file", uri);
}
public static class Scenario
{
private final Path jettyHome;
private final Path jettyBase;
private final Path war;
private final String arch;
private final String title;
private final AttributeNormalizer normalizer;
public Scenario(String arch, String title, Path jettyHome, Path jettyBase, Path war)
{
this.arch = arch;
this.title = title;
// Grab specific values of interest in general
this.jettyHome = jettyHome;
this.jettyBase = jettyBase;
this.war = war;
assertTrue(Files.exists(this.jettyHome));
assertTrue(Files.exists(this.jettyBase));
assertTrue(Files.exists(this.war));
// Set some System Properties that AttributeNormalizer expects
System.setProperty("jetty.home", jettyHome.toString());
System.setProperty("jetty.base", jettyBase.toString());
// Setup normalizer
Resource webresource = resourceFactory.newResource(war);
this.normalizer = new AttributeNormalizer(webresource);
}
@Override
public String toString()
{
return String.format("%s [%s]", this.title, this.arch);
}
}
}

View File

@ -1,64 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee10.quickstart;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class AttributeNormalizerToCanonicalUriTest
{
public static Stream<String[]> sampleUris()
{
List<String[]> data = new ArrayList<>();
// root without authority
data.add(new String[]{"file:/", "file:/"});
data.add(new String[]{"file:/F:/", "file:/F:"});
// root with empty authority
data.add(new String[]{"file:///", "file:///"});
data.add(new String[]{"file:///F:/", "file:///F:"});
// deep directory - no authority
data.add(new String[]{"file:/home/user/code/", "file:/home/user/code"});
data.add(new String[]{"file:/C:/code/", "file:/C:/code"});
// deep directory - with authority
data.add(new String[]{"file:///home/user/code/", "file:///home/user/code"});
data.add(new String[]{"file:///C:/code/", "file:///C:/code"});
// Some non-file tests
data.add(new String[]{"http://webtide.com/", "http://webtide.com/"});
data.add(new String[]{"http://webtide.com/cometd/", "http://webtide.com/cometd"});
return data.stream();
}
@ParameterizedTest
@MethodSource("sampleUris")
public void testCanonicalURI(String input, String expected)
{
URI inputURI = URI.create(input);
URI actual = AttributeNormalizer.toCanonicalURI(inputURI);
assertThat(input, actual.toASCIIString(), is(expected));
}
}

View File

@ -723,16 +723,13 @@ 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 resolve the extra classpath jars
*/
protected List<Resource> findExtraClasspathJars(WebAppContext context)
throws Exception
{
if (context == null || context.getExtraClasspath() == null)
return null;
return context.getExtraClasspath()
.getResources()
.stream()
.filter(this::isFileSupported)
.collect(Collectors.toList());
@ -775,7 +772,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
return List.of();
return context.getExtraClasspath()
.getResources()
.stream()
.filter(Resource::isDirectory)
.collect(Collectors.toList());

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
@ -43,7 +44,6 @@ 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;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
@ -113,7 +113,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
*/
boolean isParentLoaderPriority();
ResourceCollection getExtraClasspath();
List<Resource> getExtraClasspath();
boolean isServerResource(String name, URL parentUrl);
@ -193,13 +193,8 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
}
if (context.getExtraClasspath() != null)
{
for (Resource resource : context.getExtraClasspath().getResources())
{
addClassPath(resource);
}
}
for (Resource extra : context.getExtraClasspath())
addClassPath(extra);
}
/**
@ -224,16 +219,23 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
/**
* @param resources The resources to add to the classpath
* @throws IOException if unable to add classpath from resource
* @param resource The resources to add to the classpath
*/
public void addClassPath(Resource resources)
throws IOException
public void addClassPath(Resource resource)
{
for (Resource resource: resources)
for (Resource r : resource)
{
if (Resources.exists(resource))
addURL(resource.getURI().toURL());
if (resource.exists())
{
try
{
addURL(r.getURI().toURL());
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
}
}
else
{
if (LOG.isDebugEnabled())

View File

@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration.Dynamic;
@ -59,7 +60,6 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ClassLoaderDump;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
@ -132,7 +132,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
private boolean _persistTmpDir = false;
private String _war;
private ResourceCollection _extraClasspath;
private List<Resource> _extraClasspath;
private Throwable _unavailableException;
private Map<String, String> _resourceAliases;
@ -1235,29 +1235,29 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
@Override
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
public ResourceCollection getExtraClasspath()
public List<Resource> getExtraClasspath()
{
return _extraClasspath;
return _extraClasspath == null ? Collections.emptyList() : _extraClasspath;
}
/**
* Set the Extra ClassPath via delimited String.
* <p>
* This is a convenience method for {@link #setExtraClasspath(ResourceCollection)}
* 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 '/'.
* @see #setExtraClasspath(ResourceCollection)
* @see #setExtraClasspath(List)
*/
public void setExtraClasspath(String extraClasspath)
{
List<URI> uris = URIUtil.split(extraClasspath);
setExtraClasspath(this.getResourceFactory().newResource(uris));
setExtraClasspath(uris.stream().map(this.getResourceFactory()::newResource).collect(Collectors.toList()));
}
public void setExtraClasspath(ResourceCollection extraClasspath)
public void setExtraClasspath(List<Resource> extraClasspath)
{
_extraClasspath = extraClasspath;
}

View File

@ -17,8 +17,8 @@ import org.eclipse.jetty.ee9.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.ee9.webapp.Configuration;
import org.eclipse.jetty.ee9.webapp.WebAppContext;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,9 +47,9 @@ public class MavenQuickStartConfiguration extends QuickStartConfiguration
//Iterate over all of the resource bases and ignore any that were original bases, just
//deleting the overlays
Resource res = context.getBaseResource();
if (res instanceof ResourceCollection)
if (res instanceof CombinedResource)
{
for (Resource r : ((ResourceCollection)res).getResources())
for (Resource r : ((CombinedResource)res).getResources())
{
if (originalBaseStr.contains(r.toString()))
continue;

View File

@ -40,8 +40,8 @@ import org.eclipse.jetty.ee9.webapp.WebAppContext;
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
@ -220,7 +220,7 @@ public class MavenWebAppContext extends WebAppContext
* configuration
*
* @param resourceBases Array of resources strings to set as a
* {@link ResourceCollection}.
* {@link CombinedResource}.
*/
public void setResourceBases(String[] resourceBases)
{

View File

@ -29,8 +29,8 @@ import org.eclipse.jetty.ee9.quickstart.QuickStartConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
@ -105,8 +105,8 @@ public class WebAppPropertyConverter
//send over the calculated resource bases that includes unpacked overlays
Resource baseResource = webApp.getBaseResource();
if (baseResource instanceof ResourceCollection)
props.put(BASE_DIRS, toCSV(((ResourceCollection)webApp.getBaseResource()).getResources()));
if (baseResource instanceof CombinedResource)
props.put(BASE_DIRS, toCSV(((CombinedResource)webApp.getBaseResource()).getResources()));
else if (baseResource instanceof Resource)
props.put(BASE_DIRS, webApp.getBaseResource().toString());

View File

@ -25,8 +25,8 @@ import org.eclipse.jetty.ee9.webapp.WebAppContext;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.CombinedResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -152,10 +152,10 @@ public class TestWebAppPropertyConverter
assertEquals(true, webApp.isPersistTempDirectory());
assertEquals(war.getAbsolutePath(), webApp.getWar());
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
assertThat(webApp.getBaseResource(), instanceOf(ResourceCollection.class));
assertThat(webApp.getBaseResource(), instanceOf(CombinedResource.class));
ResourceCollection resourceCollection = (ResourceCollection)webApp.getBaseResource();
List<URI> actual = resourceCollection.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
CombinedResource combinedResource = (CombinedResource)webApp.getBaseResource();
List<URI> actual = combinedResource.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
assertThat(actual, containsInAnyOrder(expected));
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.AttributeNormalizer;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.ee9.webapp.WebInfConfiguration;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.AttributeNormalizer;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.xml.XmlAppendable;
import org.slf4j.Logger;

View File

@ -729,7 +729,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
return null;
return context.getExtraClasspath()
.getResources()
.stream()
.filter(this::isFileSupported)
.collect(Collectors.toList());
@ -773,7 +772,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
return List.of();
return context.getExtraClasspath()
.getResources()
.stream()
.filter(Resource::isDirectory)
.collect(Collectors.toList());

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
@ -43,7 +44,6 @@ 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;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
@ -113,7 +113,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
*/
boolean isParentLoaderPriority();
ResourceCollection getExtraClasspath();
List<Resource> getExtraClasspath();
boolean isServerResource(String name, URL parentUrl);
@ -193,13 +193,8 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
}
if (context.getExtraClasspath() != null)
{
for (Resource resource : context.getExtraClasspath().getResources())
{
addClassPath(resource);
}
}
for (Resource extra : context.getExtraClasspath())
addClassPath(extra);
}
/**
@ -224,23 +219,30 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
}
/**
* @param resources The resources to add to the classpath
* @throws IOException if unable to add classpath from resource
* @param resource The resources to add to the classpath
*/
public void addClassPath(Resource resources)
throws IOException
public void addClassPath(Resource resource)
{
for (Resource resource: resources)
for (Resource r : resource)
{
if (Resources.exists(resource))
addURL(resource.getURI().toURL());
if (resource.exists())
{
try
{
addURL(r.getURI().toURL());
}
catch (MalformedURLException e)
{
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Check resource exists and is not a nested jar: {}", resource);
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
}
}
};
}
/**

View File

@ -30,6 +30,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration.Dynamic;
@ -63,7 +64,6 @@ import org.eclipse.jetty.util.component.ClassLoaderDump;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
@ -135,7 +135,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
private boolean _persistTmpDir = false;
private String _war;
private ResourceCollection _extraClasspath;
private List<Resource> _extraClasspath;
private Throwable _unavailableException;
private Map<String, String> _resourceAliases;
@ -1233,30 +1233,30 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
*/
@Override
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
public ResourceCollection getExtraClasspath()
public List<Resource> getExtraClasspath()
{
return _extraClasspath;
return _extraClasspath == null ? Collections.emptyList() : _extraClasspath;
}
/**
* Set the Extra ClassPath via delimited String.
* <p>
* This is a convenience method for {@link #setExtraClasspath(ResourceCollection)}
* 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(ResourceCollection)
* @see #setExtraClasspath(List)
*/
public void setExtraClasspath(String extraClasspath) throws IOException
{
List<URI> uris = URIUtil.split(extraClasspath);
setExtraClasspath(this.getResourceFactory().newResource(uris));
setExtraClasspath(uris.stream().map(this.getResourceFactory()::newResource).collect(Collectors.toList()));
}
public void setExtraClasspath(ResourceCollection extraClasspath)
public void setExtraClasspath(List<Resource> extraClasspath)
{
_extraClasspath = extraClasspath;
}