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:
parent
4225054b62
commit
976ab3df8d
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.ee9.quickstart;
|
package org.eclipse.jetty.util.resource;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -22,18 +22,15 @@ import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Set;
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
if ((o1.value == null) && (o2.value != null))
|
||||||
public int compare(Attribute o1, Attribute o2)
|
|
||||||
{
|
{
|
||||||
if ((o1.value == null) && (o2.value != null))
|
return -1;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 final List<PathAttribute> paths = new ArrayList<>();
|
||||||
private Map<String, Attribute> attributes = new HashMap<>();
|
private final List<URIAttribute> uris = new ArrayList<>();
|
||||||
private List<PathAttribute> paths = new ArrayList<>();
|
|
||||||
private List<URIAttribute> uris = new ArrayList<>();
|
|
||||||
|
|
||||||
public AttributeNormalizer(Resource baseResource)
|
public AttributeNormalizer(Resource baseResource)
|
||||||
{
|
{
|
||||||
if (baseResource == null)
|
if (baseResource == null)
|
||||||
throw new IllegalArgumentException("No base resource!");
|
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.base", 9);
|
||||||
addSystemProperty("jetty.home", 8);
|
addSystemProperty("jetty.home", 8);
|
||||||
addSystemProperty("user.home", 7);
|
addSystemProperty("user.home", 7);
|
||||||
addSystemProperty("user.dir", 6);
|
addSystemProperty("user.dir", 6);
|
||||||
|
|
||||||
if (warURI.getScheme().equalsIgnoreCase("file"))
|
Set<Path> rootPaths = new HashSet<>();
|
||||||
paths.add(new PathAttribute("WAR.path", toCanonicalPath(new File(warURI).toString()), 10));
|
for (Resource r : baseResource)
|
||||||
uris.add(new URIAttribute("WAR.uri", warURI, 9)); // preferred encoding
|
{
|
||||||
uris.add(new URIAttribute("WAR", warURI, 8)); // legacy encoding
|
if (r instanceof MountedPathResource mpr && rootPaths.contains(mpr.getContainerPath()))
|
||||||
|
return;
|
||||||
|
|
||||||
Collections.sort(paths, attrComparator);
|
URI warURI = toCanonicalURI(r.getURI());
|
||||||
Collections.sort(uris, attrComparator);
|
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())
|
if (LOG.isDebugEnabled())
|
||||||
{
|
Stream.concat(paths.stream(), uris.stream()).map(Object::toString).forEach(LOG::debug);
|
||||||
for (Attribute attr : attributes.values())
|
|
||||||
{
|
|
||||||
LOG.debug(attr.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSystemProperty(String key, int weight)
|
private void addSystemProperty(String key, int weight)
|
||||||
|
@ -352,7 +347,7 @@ public class AttributeNormalizer
|
||||||
return String.format("${%s}", a.key);
|
return String.format("${%s}", a.key);
|
||||||
|
|
||||||
String s = uPath.substring(aPath.length());
|
String s = uPath.substring(aPath.length());
|
||||||
if (s.length() > 0 && s.charAt(0) != '/')
|
if (s.charAt(0) != '/')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return String.format("${%s}%s", a.key, s);
|
return String.format("${%s}%s", a.key, s);
|
||||||
|
@ -375,95 +370,79 @@ public class AttributeNormalizer
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.startsWith(a.path))
|
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();
|
return path.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String expand(String str)
|
public String expand(String str)
|
||||||
{
|
|
||||||
return expand(str, new Stack<String>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String expand(String str, Stack<String> seenStack)
|
|
||||||
{
|
{
|
||||||
if (str == null)
|
if (str == null)
|
||||||
{
|
{
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str.indexOf("${") < 0)
|
if (!str.contains("${"))
|
||||||
{
|
{
|
||||||
// Contains no potential expressions.
|
// Contains no potential expressions.
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matcher mat = __propertyPattern.matcher(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);
|
String prefix = str.substring(0, mat.start());
|
||||||
|
String property = mat.group(1);
|
||||||
// Loop detection
|
String suffix = str.substring(mat.end());
|
||||||
if (seenStack.contains(property))
|
str = expand(prefix, property, suffix);
|
||||||
{
|
|
||||||
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
|
return StringUtil.replace(str, "$$", "$");
|
||||||
expanded.append(str.substring(offset));
|
|
||||||
|
|
||||||
return StringUtil.replace(expanded.toString(), "$$", "$");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getString(String property)
|
private String expand(String prefix, String property, String suffix)
|
||||||
{
|
{
|
||||||
if (property == null)
|
if (property == null)
|
||||||
{
|
|
||||||
return 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);
|
for (PathAttribute attr : paths)
|
||||||
if (a != null)
|
{
|
||||||
return a.value;
|
if (property.equals(attr.key))
|
||||||
|
{
|
||||||
|
String path = prefix + attr.value + suffix;
|
||||||
|
if (Files.exists(Path.of(path)))
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use system properties next
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,11 +13,8 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.util.resource;
|
package org.eclipse.jetty.util.resource;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -30,32 +27,34 @@ import java.util.stream.Collectors;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of Resources.
|
* Multiple resource directories presented as a single Resource.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
public class ResourceCollection extends Resource
|
public class CombinedResource extends Resource
|
||||||
{
|
{
|
||||||
private final List<Resource> _resources;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new resource 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.
|
||||||
* @param resources the resources to be added to collection
|
* @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<>();
|
resources = CombinedResource.gatherUniqueFlatResourceList(resources);
|
||||||
gatherUniqueFlatResourceList(res, resources);
|
|
||||||
_resources = Collections.unmodifiableList(res);
|
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())
|
if (resources == null || resources.isEmpty())
|
||||||
throw new IllegalArgumentException("Empty Resource collection");
|
return null;
|
||||||
|
|
||||||
|
List<Resource> unique = new ArrayList<>(resources.size());
|
||||||
|
|
||||||
for (Resource r : resources)
|
for (Resource r : resources)
|
||||||
{
|
{
|
||||||
|
@ -64,9 +63,9 @@ public class ResourceCollection extends Resource
|
||||||
throw new IllegalArgumentException("Null Resource entry encountered");
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -89,6 +88,19 @@ public class ResourceCollection extends Resource
|
||||||
unique.add(r);
|
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;
|
ArrayList<Resource> resources = null;
|
||||||
|
|
||||||
// Attempt a simple (single) Resource lookup that exists
|
// Attempt a simple (single) Resource lookup that exists
|
||||||
Resource addedResource = null;
|
Resource resolved = null;
|
||||||
for (Resource res : _resources)
|
for (Resource res : _resources)
|
||||||
{
|
{
|
||||||
addedResource = res.resolve(subUriPath);
|
resolved = res.resolve(subUriPath);
|
||||||
if (Resources.missing(addedResource))
|
if (Resources.missing(resolved))
|
||||||
continue; // skip, doesn't exist
|
continue; // skip, doesn't exist
|
||||||
if (!addedResource.isDirectory())
|
if (!resolved.isDirectory())
|
||||||
return addedResource; // Return simple (non-directory) Resource
|
return resolved; // Return simple (non-directory) Resource
|
||||||
if (resources == null)
|
if (resources == null)
|
||||||
resources = new ArrayList<>();
|
resources = new ArrayList<>();
|
||||||
resources.add(addedResource);
|
resources.add(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resources == null)
|
if (resources == null)
|
||||||
return addedResource; // This will not exist
|
return resolved; // This will not exist
|
||||||
|
|
||||||
if (resources.size() == 1)
|
if (resources.size() == 1)
|
||||||
return resources.get(0);
|
return resources.get(0);
|
||||||
|
|
||||||
return new ResourceCollection(resources);
|
return new CombinedResource(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean exists()
|
public boolean exists()
|
||||||
{
|
{
|
||||||
for (Resource r : _resources)
|
return _resources.stream().anyMatch(Resource::exists);
|
||||||
{
|
|
||||||
if (r.exists())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Path getPath()
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName()
|
public String getName()
|
||||||
{
|
{
|
||||||
for (Resource r : _resources)
|
|
||||||
{
|
|
||||||
String name = r.getName();
|
|
||||||
if (name != null)
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFileName()
|
public String getFileName()
|
||||||
{
|
{
|
||||||
|
String filename = null;
|
||||||
|
// return a non-null filename only if all resources agree on the same name.
|
||||||
for (Resource r : _resources)
|
for (Resource r : _resources)
|
||||||
{
|
{
|
||||||
String filename = r.getFileName();
|
String fn = r.getFileName();
|
||||||
if (filename != null)
|
if (fn == null)
|
||||||
{
|
return null;
|
||||||
return filename;
|
if (filename == null)
|
||||||
}
|
filename = fn;
|
||||||
|
else if (!filename.equals(fn))
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getURI()
|
public URI getURI()
|
||||||
{
|
{
|
||||||
for (Resource r : _resources)
|
|
||||||
{
|
|
||||||
URI uri = r.getURI();
|
|
||||||
if (uri != null)
|
|
||||||
{
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +278,7 @@ public class ResourceCollection extends Resource
|
||||||
return true;
|
return true;
|
||||||
if (o == null || getClass() != o.getClass())
|
if (o == null || getClass() != o.getClass())
|
||||||
return false;
|
return false;
|
||||||
ResourceCollection other = (ResourceCollection)o;
|
CombinedResource other = (CombinedResource)o;
|
||||||
return Objects.equals(_resources, other._resources);
|
return Objects.equals(_resources, other._resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +295,7 @@ public class ResourceCollection extends Resource
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return _resources.stream()
|
return _resources.stream()
|
||||||
.map(Resource::getName)
|
.map(Resource::toString)
|
||||||
.collect(Collectors.joining(", ", "[", "]"));
|
.collect(Collectors.joining(", ", "[", "]"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,11 +113,8 @@ public abstract class Resource implements Iterable<Resource>
|
||||||
public abstract boolean isContainedIn(Resource r);
|
public abstract boolean isContainedIn(Resource r);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an Iterator of all Resource's referenced in this Resource.
|
* <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>
|
||||||
* <p>
|
|
||||||
* This is meaningful if you have a Composite Resource, otherwise it will be a single entry Iterator.
|
|
||||||
* </p>
|
|
||||||
*
|
*
|
||||||
* @return the iterator of Resources.
|
* @return the iterator of Resources.
|
||||||
*/
|
*/
|
||||||
|
@ -198,22 +195,28 @@ public abstract class Resource implements Iterable<Resource>
|
||||||
/**
|
/**
|
||||||
* Creates a new input stream to the resource.
|
* Creates a new input stream to the resource.
|
||||||
*
|
*
|
||||||
* @return an input stream to the resource
|
* @return an input stream to the resource or null if one is not available.
|
||||||
* @throws IOException if unable to open the input stream
|
* @throws IOException if there is a problem opening the input stream
|
||||||
*/
|
*/
|
||||||
public InputStream newInputStream() throws IOException
|
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.
|
* 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.
|
* @throws IOException if unable to open the readable bytechannel for the resource.
|
||||||
*/
|
*/
|
||||||
public ReadableByteChannel newReadableByteChannel() throws IOException
|
public ReadableByteChannel newReadableByteChannel() throws IOException
|
||||||
{
|
{
|
||||||
|
Path path = getPath();
|
||||||
|
if (path == null)
|
||||||
|
return null;
|
||||||
return Files.newByteChannel(getPath(), StandardOpenOption.READ);
|
return Files.newByteChannel(getPath(), StandardOpenOption.READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,27 +35,23 @@ public interface ResourceFactory
|
||||||
/**
|
/**
|
||||||
* <p>Make a Resource containing a collection of other resources</p>
|
* <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.
|
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
|
||||||
* @return A Resource of multiple resources.
|
* @return A Resource of multiple resources or a single resource if only 1 is passed, or null if none are passed
|
||||||
* @see ResourceCollection
|
* @see CombinedResource
|
||||||
*/
|
*/
|
||||||
static ResourceCollection combine(List<Resource> resources)
|
static Resource combine(List<Resource> resources)
|
||||||
{
|
{
|
||||||
if (resources == null || resources.isEmpty())
|
return CombinedResource.combine(resources);
|
||||||
throw new IllegalArgumentException("No resources");
|
|
||||||
return new ResourceCollection(resources);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Make a Resource containing a collection of other resources</p>
|
* <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.
|
* @param resources multiple resources to combine as a single resource. Typically, they are directories.
|
||||||
* @return A Resource of multiple resources.
|
* @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)
|
return CombinedResource.combine(List.of(resources));
|
||||||
throw new IllegalArgumentException("No resources");
|
|
||||||
return new ResourceCollection(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
|
* @param uris the URIs
|
||||||
* @return the Resource for the provided path
|
* @return the Resource for the provided path
|
||||||
*/
|
*/
|
||||||
default ResourceCollection newResource(List<URI> uris)
|
default Resource newResource(List<URI> uris)
|
||||||
{
|
{
|
||||||
if ((uris == null) || (uris.isEmpty()))
|
if ((uris == null) || (uris.isEmpty()))
|
||||||
throw new IllegalArgumentException("List of URIs is invalid");
|
throw new IllegalArgumentException("List of URIs is invalid");
|
||||||
|
|
|
@ -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.net.URI;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
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.FS;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.util.IO;
|
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.AfterAll;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Assumptions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
@ -43,7 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class AttributeNormalizerTest
|
public class AttributeNormalizerTest
|
||||||
{
|
{
|
||||||
public static Stream<Arguments> scenarios()
|
public static Stream<Arguments> scenarios() throws IOException
|
||||||
{
|
{
|
||||||
final List<Arguments> data = new ArrayList<>();
|
final List<Arguments> data = new ArrayList<>();
|
||||||
final String arch = String.format("%s/%s", System.getProperty("os.name"), System.getProperty("os.arch"));
|
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";
|
title = "Typical Setup";
|
||||||
jettyHome = asTargetPath(title, "jetty-distro");
|
jettyHome = asTargetPath(title, "jetty-typical");
|
||||||
jettyBase = asTargetPath(title, "jetty-distro/demo.base");
|
jettyBase = asTargetPath(title, "jetty-typical/demo.base");
|
||||||
war = asTargetPath(title, "jetty-distro/demo.base/webapps/FOO");
|
war = asTargetPath(title, "jetty-typical/demo.base/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))));
|
||||||
|
|
||||||
// ------
|
// ------
|
||||||
title = "Old Setup";
|
title = "Old Setup";
|
||||||
jettyHome = asTargetPath(title, "jetty-distro");
|
jettyHome = asTargetPath(title, "jetty-old");
|
||||||
jettyBase = asTargetPath(title, "jetty-distro");
|
jettyBase = asTargetPath(title, "jetty-old");
|
||||||
war = asTargetPath(title, "jetty-distro/webapps/FOO");
|
war = asTargetPath(title, "jetty-old/webapps/FOO");
|
||||||
|
if (!Files.exists(war.resolve("index.html")))
|
||||||
data.add(Arguments.of(new Scenario(arch, title, jettyHome, jettyBase, war)));
|
{
|
||||||
|
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
|
// This puts the jetty.home inside the jetty.base
|
||||||
|
@ -74,8 +77,7 @@ public class AttributeNormalizerTest
|
||||||
jettyHome = asTargetPath(title, "app/dist");
|
jettyHome = asTargetPath(title, "app/dist");
|
||||||
jettyBase = asTargetPath(title, "app");
|
jettyBase = asTargetPath(title, "app");
|
||||||
war = asTargetPath(title, "app/webapps/FOO");
|
war = asTargetPath(title, "app/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)));
|
|
||||||
|
|
||||||
// ------
|
// ------
|
||||||
// This tests a path scenario often seen on various automatic deployments tooling
|
// 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");
|
jettyHome = asTargetPath(title, "app%2Fnasty/dist");
|
||||||
jettyBase = asTargetPath(title, "app%2Fnasty/base");
|
jettyBase = asTargetPath(title, "app%2Fnasty/base");
|
||||||
war = asTargetPath(title, "app%2Fnasty/base/webapps/FOO");
|
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();
|
return data.stream();
|
||||||
}
|
}
|
||||||
|
@ -166,7 +176,9 @@ public class AttributeNormalizerTest
|
||||||
public void testNormalizeWarAsString(final Scenario scenario)
|
public void testNormalizeWarAsString(final Scenario scenario)
|
||||||
{
|
{
|
||||||
// Normalize WAR as String path
|
// 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
|
@ParameterizedTest
|
||||||
|
@ -259,12 +271,22 @@ public class AttributeNormalizerTest
|
||||||
assertExpandPath(scenario, "${jetty.home}", scenario.jettyHome.toString());
|
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
|
@ParameterizedTest
|
||||||
@MethodSource("scenarios")
|
@MethodSource("scenarios")
|
||||||
public void testNormalizeWarAsURI(final Scenario scenario)
|
public void testNormalizeWarAsURI(final Scenario scenario)
|
||||||
{
|
{
|
||||||
// Normalize WAR as URI
|
// Normalize WAR as URI
|
||||||
URI testWarURI = scenario.war.toUri();
|
URI testWarURI = scenario.war.getURI();
|
||||||
|
Assumptions.assumeTrue(testWarURI != null);
|
||||||
assertNormalize(scenario, testWarURI, "${WAR.uri}");
|
assertNormalize(scenario, testWarURI, "${WAR.uri}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +295,7 @@ public class AttributeNormalizerTest
|
||||||
public void testNormalizeWarDeepAsPath(final Scenario scenario)
|
public void testNormalizeWarDeepAsPath(final Scenario scenario)
|
||||||
{
|
{
|
||||||
// Normalize WAR deep path as File
|
// 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"));
|
assertNormalize(scenario, testWarDeep, "${WAR.path}" + FS.separators("/deep/ref"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +304,7 @@ public class AttributeNormalizerTest
|
||||||
public void testNormalizeWarDeepAsString(final Scenario scenario)
|
public void testNormalizeWarDeepAsString(final Scenario scenario)
|
||||||
{
|
{
|
||||||
// Normalize WAR deep path as String
|
// 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());
|
assertNormalize(scenario, testWarDeep.toString(), testWarDeep.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,30 +313,22 @@ public class AttributeNormalizerTest
|
||||||
public void testNormalizeWarDeepAsURI(final Scenario scenario)
|
public void testNormalizeWarDeepAsURI(final Scenario scenario)
|
||||||
{
|
{
|
||||||
// Normalize WAR deep path as URI
|
// 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");
|
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
|
public static class Scenario
|
||||||
{
|
{
|
||||||
private final Path jettyHome;
|
private final Path jettyHome;
|
||||||
private final Path jettyBase;
|
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 arch;
|
||||||
private final String title;
|
private final String title;
|
||||||
private final AttributeNormalizer normalizer;
|
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.arch = arch;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
|
@ -326,15 +340,48 @@ public class AttributeNormalizerTest
|
||||||
|
|
||||||
assertTrue(Files.exists(this.jettyHome));
|
assertTrue(Files.exists(this.jettyHome));
|
||||||
assertTrue(Files.exists(this.jettyBase));
|
assertTrue(Files.exists(this.jettyBase));
|
||||||
assertTrue(Files.exists(this.war));
|
assertTrue(war.exists());
|
||||||
|
|
||||||
// Set some System Properties that AttributeNormalizer expects
|
// Set some System Properties that AttributeNormalizer expects
|
||||||
System.setProperty("jetty.home", jettyHome.toString());
|
System.setProperty("jetty.home", jettyHome.toString());
|
||||||
System.setProperty("jetty.base", jettyBase.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
|
// Setup normalizer
|
||||||
Resource webresource = resourceFactory.newResource(war);
|
this.normalizer = new AttributeNormalizer(war);
|
||||||
this.normalizer = new AttributeNormalizer(webresource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.ee9.quickstart;
|
package org.eclipse.jetty.util.resource;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
|
@ -52,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@ExtendWith(WorkDirExtension.class)
|
@ExtendWith(WorkDirExtension.class)
|
||||||
public class ResourceCollectionTest
|
public class CombinedResourceTest
|
||||||
{
|
{
|
||||||
private final ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
|
private final ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable();
|
||||||
public WorkDir workDir;
|
public WorkDir workDir;
|
||||||
|
@ -78,7 +78,7 @@ public class ResourceCollectionTest
|
||||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||||
|
|
||||||
ResourceCollection rc = ResourceFactory.combine(
|
Resource rc = ResourceFactory.combine(
|
||||||
resourceFactory.newResource(one),
|
resourceFactory.newResource(one),
|
||||||
resourceFactory.newResource(two),
|
resourceFactory.newResource(two),
|
||||||
resourceFactory.newResource(three)
|
resourceFactory.newResource(three)
|
||||||
|
@ -127,7 +127,7 @@ public class ResourceCollectionTest
|
||||||
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
Path two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||||
|
|
||||||
ResourceCollection rc = ResourceFactory.combine(
|
Resource rc = ResourceFactory.combine(
|
||||||
resourceFactory.newResource(one),
|
resourceFactory.newResource(one),
|
||||||
resourceFactory.newResource(two),
|
resourceFactory.newResource(two),
|
||||||
resourceFactory.newResource(three)
|
resourceFactory.newResource(three)
|
||||||
|
@ -135,8 +135,8 @@ public class ResourceCollectionTest
|
||||||
|
|
||||||
// This should return a ResourceCollection with 3 `/dir/` sub-directories.
|
// This should return a ResourceCollection with 3 `/dir/` sub-directories.
|
||||||
Resource r = rc.resolve("dir");
|
Resource r = rc.resolve("dir");
|
||||||
assertTrue(r instanceof ResourceCollection);
|
assertTrue(r instanceof CombinedResource);
|
||||||
rc = (ResourceCollection)r;
|
rc = (CombinedResource)r;
|
||||||
assertEquals(getContent(rc, "1.txt"), "1 - one (in dir)");
|
assertEquals(getContent(rc, "1.txt"), "1 - one (in dir)");
|
||||||
assertEquals(getContent(rc, "2.txt"), "2 - two (in dir)");
|
assertEquals(getContent(rc, "2.txt"), "2 - two (in dir)");
|
||||||
assertEquals(getContent(rc, "3.txt"), "3 - three (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 two = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two");
|
||||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||||
|
|
||||||
ResourceCollection rc = ResourceFactory.combine(
|
Resource rc = ResourceFactory.combine(
|
||||||
resourceFactory.newResource(one),
|
resourceFactory.newResource(one),
|
||||||
resourceFactory.newResource(two),
|
resourceFactory.newResource(two),
|
||||||
resourceFactory.newResource(three)
|
resourceFactory.newResource(three)
|
||||||
|
@ -175,7 +175,7 @@ public class ResourceCollectionTest
|
||||||
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
Path three = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/three");
|
||||||
Path twoDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two/dir");
|
Path twoDir = MavenTestingUtils.getTestResourcePathDir("org/eclipse/jetty/util/resource/two/dir");
|
||||||
|
|
||||||
ResourceCollection rc1 = ResourceFactory.combine(
|
Resource rc1 = ResourceFactory.combine(
|
||||||
List.of(
|
List.of(
|
||||||
resourceFactory.newResource(one),
|
resourceFactory.newResource(one),
|
||||||
resourceFactory.newResource(two),
|
resourceFactory.newResource(two),
|
||||||
|
@ -183,7 +183,7 @@ public class ResourceCollectionTest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
ResourceCollection rc2 = ResourceFactory.combine(
|
Resource rc2 = ResourceFactory.combine(
|
||||||
List.of(
|
List.of(
|
||||||
// the original ResourceCollection
|
// the original ResourceCollection
|
||||||
rc1,
|
rc1,
|
||||||
|
@ -202,9 +202,11 @@ public class ResourceCollectionTest
|
||||||
};
|
};
|
||||||
|
|
||||||
List<URI> actual = new ArrayList<>();
|
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));
|
assertThat(actual, contains(expected));
|
||||||
}
|
}
|
||||||
|
@ -307,7 +309,7 @@ public class ResourceCollectionTest
|
||||||
// Since this is user space, we cannot know ahead of time what
|
// Since this is user space, we cannot know ahead of time what
|
||||||
// this list contains, so we mount because we assume there
|
// this list contains, so we mount because we assume there
|
||||||
// will be necessary things to mount
|
// will be necessary things to mount
|
||||||
ResourceCollection rc = resourceFactory.newResource(uris);
|
Resource rc = resourceFactory.newResource(uris);
|
||||||
assertThat(getContent(rc, "test.txt"), is("Test"));
|
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
|
// Since this is user space, we cannot know ahead of time what
|
||||||
// this list contains, so we mount because we assume there
|
// this list contains, so we mount because we assume there
|
||||||
// will be necessary things to mount
|
// 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, "test.txt"), is("Test inside lib-foo.jar"));
|
||||||
assertThat(getContent(rc, "testZed.txt"), is("TestZed inside lib-zed.jar"));
|
assertThat(getContent(rc, "testZed.txt"), is("TestZed inside lib-zed.jar"));
|
||||||
}
|
}
|
|
@ -17,8 +17,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
|
||||||
import org.eclipse.jetty.ee10.webapp.Configuration;
|
import org.eclipse.jetty.ee10.webapp.Configuration;
|
||||||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.util.IO;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
//Iterate over all of the resource bases and ignore any that were original bases, just
|
||||||
//deleting the overlays
|
//deleting the overlays
|
||||||
Resource res = context.getBaseResource();
|
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()))
|
if (originalBaseStr.contains(r.toString()))
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -39,8 +39,8 @@ import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.util.FileID;
|
import org.eclipse.jetty.util.FileID;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -218,7 +218,7 @@ public class MavenWebAppContext extends WebAppContext
|
||||||
* configuration
|
* configuration
|
||||||
*
|
*
|
||||||
* @param resourceBases Array of resources strings to set as a
|
* @param resourceBases Array of resources strings to set as a
|
||||||
* {@link ResourceCollection}.
|
* {@link CombinedResource}.
|
||||||
*/
|
*/
|
||||||
public void setResourceBases(String[] resourceBases)
|
public void setResourceBases(String[] resourceBases)
|
||||||
{
|
{
|
||||||
|
|
|
@ -30,8 +30,8 @@ import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,8 +106,8 @@ public class WebAppPropertyConverter
|
||||||
|
|
||||||
//send over the calculated resource bases that includes unpacked overlays
|
//send over the calculated resource bases that includes unpacked overlays
|
||||||
Resource baseResource = webApp.getBaseResource();
|
Resource baseResource = webApp.getBaseResource();
|
||||||
if (baseResource instanceof ResourceCollection)
|
if (baseResource instanceof CombinedResource)
|
||||||
props.put(BASE_DIRS, toCSV(((ResourceCollection)webApp.getBaseResource()).getResources()));
|
props.put(BASE_DIRS, toCSV(((CombinedResource)webApp.getBaseResource()).getResources()));
|
||||||
else if (baseResource instanceof Resource)
|
else if (baseResource instanceof Resource)
|
||||||
props.put(BASE_DIRS, webApp.getBaseResource().toString());
|
props.put(BASE_DIRS, webApp.getBaseResource().toString());
|
||||||
|
|
||||||
|
|
|
@ -16,17 +16,17 @@ package org.eclipse.jetty.ee10.maven.plugin;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.util.IO;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -152,10 +152,13 @@ public class TestWebAppPropertyConverter
|
||||||
assertEquals(true, webApp.isPersistTempDirectory());
|
assertEquals(true, webApp.isPersistTempDirectory());
|
||||||
assertEquals(war.getAbsolutePath(), webApp.getWar());
|
assertEquals(war.getAbsolutePath(), webApp.getWar());
|
||||||
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
|
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
|
||||||
assertThat(webApp.getBaseResource(), instanceOf(ResourceCollection.class));
|
assertThat(webApp.getBaseResource(), instanceOf(CombinedResource.class));
|
||||||
|
|
||||||
ResourceCollection resourceCollection = (ResourceCollection)webApp.getBaseResource();
|
Resource combinedResource = webApp.getBaseResource();
|
||||||
List<URI> actual = resourceCollection.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
|
List<URI> actual = new ArrayList<>();
|
||||||
|
for (Resource r : combinedResource)
|
||||||
|
if (r != null)
|
||||||
|
actual.add(r.getURI());
|
||||||
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
|
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
|
||||||
assertThat(actual, containsInAnyOrder(expected));
|
assertThat(actual, containsInAnyOrder(expected));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
|
|
|
@ -57,6 +57,7 @@ import org.eclipse.jetty.ee10.webapp.WebInfConfiguration;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.resource.AttributeNormalizer;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
import org.eclipse.jetty.xml.XmlAppendable;
|
import org.eclipse.jetty.xml.XmlAppendable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -723,16 +723,13 @@ public class MetaInfConfiguration extends AbstractConfiguration
|
||||||
*
|
*
|
||||||
* @param context the context to find extra classpath jars in
|
* @param context the context to find extra classpath jars in
|
||||||
* @return the list of Resources with the extra classpath, or null if not found
|
* @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)
|
protected List<Resource> findExtraClasspathJars(WebAppContext context)
|
||||||
throws Exception
|
|
||||||
{
|
{
|
||||||
if (context == null || context.getExtraClasspath() == null)
|
if (context == null || context.getExtraClasspath() == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return context.getExtraClasspath()
|
return context.getExtraClasspath()
|
||||||
.getResources()
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(this::isFileSupported)
|
.filter(this::isFileSupported)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -775,7 +772,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
|
||||||
return List.of();
|
return List.of();
|
||||||
|
|
||||||
return context.getExtraClasspath()
|
return context.getExtraClasspath()
|
||||||
.getResources()
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(Resource::isDirectory)
|
.filter(Resource::isDirectory)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.lang.instrument.IllegalClassFormatException;
|
import java.lang.instrument.IllegalClassFormatException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
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.TypeUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
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.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -113,7 +113,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
*/
|
*/
|
||||||
boolean isParentLoaderPriority();
|
boolean isParentLoaderPriority();
|
||||||
|
|
||||||
ResourceCollection getExtraClasspath();
|
List<Resource> getExtraClasspath();
|
||||||
|
|
||||||
boolean isServerResource(String name, URL parentUrl);
|
boolean isServerResource(String name, URL parentUrl);
|
||||||
|
|
||||||
|
@ -193,13 +193,8 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.getExtraClasspath() != null)
|
for (Resource extra : context.getExtraClasspath())
|
||||||
{
|
addClassPath(extra);
|
||||||
for (Resource resource : context.getExtraClasspath().getResources())
|
|
||||||
{
|
|
||||||
addClassPath(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,16 +219,23 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resources The resources to add to the classpath
|
* @param resource The resources to add to the classpath
|
||||||
* @throws IOException if unable to add classpath from resource
|
|
||||||
*/
|
*/
|
||||||
public void addClassPath(Resource resources)
|
public void addClassPath(Resource resource)
|
||||||
throws IOException
|
|
||||||
{
|
{
|
||||||
for (Resource resource: resources)
|
for (Resource r : resource)
|
||||||
{
|
{
|
||||||
if (Resources.exists(resource))
|
if (resource.exists())
|
||||||
addURL(resource.getURI().toURL());
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addURL(r.getURI().toURL());
|
||||||
|
}
|
||||||
|
catch (MalformedURLException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
import jakarta.servlet.ServletRegistration.Dynamic;
|
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.ClassLoaderDump;
|
||||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
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.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -132,7 +132,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
||||||
private boolean _persistTmpDir = false;
|
private boolean _persistTmpDir = false;
|
||||||
|
|
||||||
private String _war;
|
private String _war;
|
||||||
private ResourceCollection _extraClasspath;
|
private List<Resource> _extraClasspath;
|
||||||
private Throwable _unavailableException;
|
private Throwable _unavailableException;
|
||||||
|
|
||||||
private Map<String, String> _resourceAliases;
|
private Map<String, String> _resourceAliases;
|
||||||
|
@ -1235,29 +1235,29 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
|
@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.
|
* Set the Extra ClassPath via delimited String.
|
||||||
* <p>
|
* <p>
|
||||||
* This is a convenience method for {@link #setExtraClasspath(ResourceCollection)}
|
* This is a convenience method for {@link #setExtraClasspath(List)}
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
|
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
|
||||||
* pointing to directories or jar files. Directories should end
|
* pointing to directories or jar files. Directories should end
|
||||||
* with '/'.
|
* with '/'.
|
||||||
* @see #setExtraClasspath(ResourceCollection)
|
* @see #setExtraClasspath(List)
|
||||||
*/
|
*/
|
||||||
public void setExtraClasspath(String extraClasspath)
|
public void setExtraClasspath(String extraClasspath)
|
||||||
{
|
{
|
||||||
List<URI> uris = URIUtil.split(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;
|
_extraClasspath = extraClasspath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ import org.eclipse.jetty.ee9.quickstart.QuickStartConfiguration;
|
||||||
import org.eclipse.jetty.ee9.webapp.Configuration;
|
import org.eclipse.jetty.ee9.webapp.Configuration;
|
||||||
import org.eclipse.jetty.ee9.webapp.WebAppContext;
|
import org.eclipse.jetty.ee9.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.util.IO;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
//Iterate over all of the resource bases and ignore any that were original bases, just
|
||||||
//deleting the overlays
|
//deleting the overlays
|
||||||
Resource res = context.getBaseResource();
|
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()))
|
if (originalBaseStr.contains(r.toString()))
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -40,8 +40,8 @@ import org.eclipse.jetty.ee9.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.util.FileID;
|
import org.eclipse.jetty.util.FileID;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -220,7 +220,7 @@ public class MavenWebAppContext extends WebAppContext
|
||||||
* configuration
|
* configuration
|
||||||
*
|
*
|
||||||
* @param resourceBases Array of resources strings to set as a
|
* @param resourceBases Array of resources strings to set as a
|
||||||
* {@link ResourceCollection}.
|
* {@link CombinedResource}.
|
||||||
*/
|
*/
|
||||||
public void setResourceBases(String[] resourceBases)
|
public void setResourceBases(String[] resourceBases)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,8 +29,8 @@ import org.eclipse.jetty.ee9.quickstart.QuickStartConfiguration;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,8 +105,8 @@ public class WebAppPropertyConverter
|
||||||
|
|
||||||
//send over the calculated resource bases that includes unpacked overlays
|
//send over the calculated resource bases that includes unpacked overlays
|
||||||
Resource baseResource = webApp.getBaseResource();
|
Resource baseResource = webApp.getBaseResource();
|
||||||
if (baseResource instanceof ResourceCollection)
|
if (baseResource instanceof CombinedResource)
|
||||||
props.put(BASE_DIRS, toCSV(((ResourceCollection)webApp.getBaseResource()).getResources()));
|
props.put(BASE_DIRS, toCSV(((CombinedResource)webApp.getBaseResource()).getResources()));
|
||||||
else if (baseResource instanceof Resource)
|
else if (baseResource instanceof Resource)
|
||||||
props.put(BASE_DIRS, webApp.getBaseResource().toString());
|
props.put(BASE_DIRS, webApp.getBaseResource().toString());
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ import org.eclipse.jetty.ee9.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.util.IO;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -152,10 +152,10 @@ public class TestWebAppPropertyConverter
|
||||||
assertEquals(true, webApp.isPersistTempDirectory());
|
assertEquals(true, webApp.isPersistTempDirectory());
|
||||||
assertEquals(war.getAbsolutePath(), webApp.getWar());
|
assertEquals(war.getAbsolutePath(), webApp.getWar());
|
||||||
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
|
assertEquals(webXml.getAbsolutePath(), webApp.getDescriptor());
|
||||||
assertThat(webApp.getBaseResource(), instanceOf(ResourceCollection.class));
|
assertThat(webApp.getBaseResource(), instanceOf(CombinedResource.class));
|
||||||
|
|
||||||
ResourceCollection resourceCollection = (ResourceCollection)webApp.getBaseResource();
|
CombinedResource combinedResource = (CombinedResource)webApp.getBaseResource();
|
||||||
List<URI> actual = resourceCollection.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
|
List<URI> actual = combinedResource.getResources().stream().filter(Objects::nonNull).map(Resource::getURI).toList();
|
||||||
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
|
URI[] expected = new URI[]{base1.toURI(), base2.toURI()};
|
||||||
assertThat(actual, containsInAnyOrder(expected));
|
assertThat(actual, containsInAnyOrder(expected));
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
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.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
|
|
|
@ -57,6 +57,7 @@ import org.eclipse.jetty.ee9.webapp.WebInfConfiguration;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.resource.AttributeNormalizer;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
import org.eclipse.jetty.xml.XmlAppendable;
|
import org.eclipse.jetty.xml.XmlAppendable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
@ -729,7 +729,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return context.getExtraClasspath()
|
return context.getExtraClasspath()
|
||||||
.getResources()
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(this::isFileSupported)
|
.filter(this::isFileSupported)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
@ -773,7 +772,6 @@ public class MetaInfConfiguration extends AbstractConfiguration
|
||||||
return List.of();
|
return List.of();
|
||||||
|
|
||||||
return context.getExtraClasspath()
|
return context.getExtraClasspath()
|
||||||
.getResources()
|
|
||||||
.stream()
|
.stream()
|
||||||
.filter(Resource::isDirectory)
|
.filter(Resource::isDirectory)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.lang.instrument.IllegalClassFormatException;
|
import java.lang.instrument.IllegalClassFormatException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
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.TypeUtil;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
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.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -113,7 +113,7 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
*/
|
*/
|
||||||
boolean isParentLoaderPriority();
|
boolean isParentLoaderPriority();
|
||||||
|
|
||||||
ResourceCollection getExtraClasspath();
|
List<Resource> getExtraClasspath();
|
||||||
|
|
||||||
boolean isServerResource(String name, URL parentUrl);
|
boolean isServerResource(String name, URL parentUrl);
|
||||||
|
|
||||||
|
@ -193,13 +193,8 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.getExtraClasspath() != null)
|
for (Resource extra : context.getExtraClasspath())
|
||||||
{
|
addClassPath(extra);
|
||||||
for (Resource resource : context.getExtraClasspath().getResources())
|
|
||||||
{
|
|
||||||
addClassPath(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,23 +219,30 @@ public class WebAppClassLoader extends URLClassLoader implements ClassVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resources The resources to add to the classpath
|
* @param resource The resources to add to the classpath
|
||||||
* @throws IOException if unable to add classpath from resource
|
|
||||||
*/
|
*/
|
||||||
public void addClassPath(Resource resources)
|
public void addClassPath(Resource resource)
|
||||||
throws IOException
|
|
||||||
{
|
{
|
||||||
for (Resource resource: resources)
|
for (Resource r : resource)
|
||||||
{
|
{
|
||||||
if (Resources.exists(resource))
|
if (resource.exists())
|
||||||
addURL(resource.getURI().toURL());
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addURL(r.getURI().toURL());
|
||||||
|
}
|
||||||
|
catch (MalformedURLException e)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Check resource exists and is not a nested jar: {}", resource);
|
LOG.debug("Check resource exists and is not a nested jar: {}", resource);
|
||||||
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
|
throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: " + resource);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.ServletContext;
|
import jakarta.servlet.ServletContext;
|
||||||
import jakarta.servlet.ServletRegistration.Dynamic;
|
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.Dumpable;
|
||||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
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.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -135,7 +135,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
||||||
private boolean _persistTmpDir = false;
|
private boolean _persistTmpDir = false;
|
||||||
|
|
||||||
private String _war;
|
private String _war;
|
||||||
private ResourceCollection _extraClasspath;
|
private List<Resource> _extraClasspath;
|
||||||
private Throwable _unavailableException;
|
private Throwable _unavailableException;
|
||||||
|
|
||||||
private Map<String, String> _resourceAliases;
|
private Map<String, String> _resourceAliases;
|
||||||
|
@ -1233,30 +1233,30 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
|
@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.
|
* Set the Extra ClassPath via delimited String.
|
||||||
* <p>
|
* <p>
|
||||||
* This is a convenience method for {@link #setExtraClasspath(ResourceCollection)}
|
* This is a convenience method for {@link #setExtraClasspath(List)} )}
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
|
* @param extraClasspath Comma or semicolon separated path of filenames or URLs
|
||||||
* pointing to directories or jar files. Directories should end
|
* pointing to directories or jar files. Directories should end
|
||||||
* with '/'.
|
* with '/'.
|
||||||
* @throws IOException if unable to resolve the resources referenced
|
* @throws IOException if unable to resolve the resources referenced
|
||||||
* @see #setExtraClasspath(ResourceCollection)
|
* @see #setExtraClasspath(List)
|
||||||
*/
|
*/
|
||||||
public void setExtraClasspath(String extraClasspath) throws IOException
|
public void setExtraClasspath(String extraClasspath) throws IOException
|
||||||
{
|
{
|
||||||
List<URI> uris = URIUtil.split(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;
|
_extraClasspath = extraClasspath;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue