Fix #12104 by returning a CombinedResource for a ClassLoader resource that has multiple directory matches.
This commit is contained in:
parent
fe6b14b8fb
commit
abab5949ea
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.util.resource;
|
package org.eclipse.jetty.util.resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -20,11 +21,11 @@ import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.util.FileID;
|
import org.eclipse.jetty.util.FileID;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
@ -197,7 +198,7 @@ public interface ResourceFactory
|
||||||
*
|
*
|
||||||
* @param resource the resource name to find in a classloader
|
* @param resource the resource name to find in a classloader
|
||||||
* @param searchSystemClassLoader true to search {@link ClassLoader#getSystemResource(String)}, false to skip
|
* @param searchSystemClassLoader true to search {@link ClassLoader#getSystemResource(String)}, false to skip
|
||||||
* @return The new Resource
|
* @return The new Resource, which may be a {@link CombinedResource} if multiple directory resources are found.
|
||||||
* @throws IllegalArgumentException if resource name or resulting URL from ClassLoader is invalid.
|
* @throws IllegalArgumentException if resource name or resulting URL from ClassLoader is invalid.
|
||||||
*/
|
*/
|
||||||
default Resource newClassLoaderResource(String resource, boolean searchSystemClassLoader)
|
default Resource newClassLoaderResource(String resource, boolean searchSystemClassLoader)
|
||||||
|
@ -205,47 +206,60 @@ public interface ResourceFactory
|
||||||
if (StringUtil.isBlank(resource))
|
if (StringUtil.isBlank(resource))
|
||||||
throw new IllegalArgumentException("Resource String is invalid: " + resource);
|
throw new IllegalArgumentException("Resource String is invalid: " + resource);
|
||||||
|
|
||||||
URL url = null;
|
// We need a local interface to combine static and non-static methods
|
||||||
|
interface Source
|
||||||
List<Function<String, URL>> loaders = new ArrayList<>();
|
|
||||||
loaders.add(Thread.currentThread().getContextClassLoader()::getResource);
|
|
||||||
loaders.add(ResourceFactory.class.getClassLoader()::getResource);
|
|
||||||
if (searchSystemClassLoader)
|
|
||||||
loaders.add(ClassLoader::getSystemResource);
|
|
||||||
|
|
||||||
for (Function<String, URL> loader : loaders)
|
|
||||||
{
|
{
|
||||||
if (url != null)
|
Enumeration<URL> getResources(String name) throws IOException;
|
||||||
break;
|
}
|
||||||
|
|
||||||
try
|
List<Source> sources = new ArrayList<>();
|
||||||
|
sources.add(Thread.currentThread().getContextClassLoader()::getResources);
|
||||||
|
sources.add(ResourceFactory.class.getClassLoader()::getResources);
|
||||||
|
if (searchSystemClassLoader)
|
||||||
|
sources.add(ClassLoader::getSystemResources);
|
||||||
|
|
||||||
|
List<Resource> resources = new ArrayList<>();
|
||||||
|
String[] names = resource.startsWith("/") ? new String[] {resource, resource.substring(1)} : new String[] {resource};
|
||||||
|
|
||||||
|
// For each source of resource
|
||||||
|
for (Source source : sources)
|
||||||
|
{
|
||||||
|
// for each variation of the resource name
|
||||||
|
for (String name : names)
|
||||||
{
|
{
|
||||||
url = loader.apply(resource);
|
try
|
||||||
if (url == null && resource.startsWith("/"))
|
{
|
||||||
url = loader.apply(resource.substring(1));
|
// Get all matching URLs
|
||||||
}
|
Enumeration<URL> urls = source.getResources(name);
|
||||||
catch (IllegalArgumentException e)
|
while (urls.hasMoreElements())
|
||||||
{
|
{
|
||||||
// Catches scenario where a bad Windows path like "C:\dev" is
|
// Get the resource
|
||||||
// improperly escaped, which various downstream classloaders
|
Resource r = newResource(urls.nextElement().toURI());
|
||||||
// tend to have a problem with
|
// If it is not a directory, then return it as the singular found resource
|
||||||
if (LOG.isTraceEnabled())
|
if (!r.isDirectory())
|
||||||
LOG.trace("Ignoring bad getResource(): {}", resource, e);
|
return r;
|
||||||
|
// otherwise add it to a list of resource to combine.
|
||||||
|
resources.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Catches scenario where a bad Windows path like "C:\dev" is
|
||||||
|
// improperly escaped, which various downstream classloaders
|
||||||
|
// tend to have a problem with
|
||||||
|
if (LOG.isTraceEnabled())
|
||||||
|
LOG.trace("Ignoring bad getResource(): {}", resource, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url == null)
|
if (resources.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
try
|
if (resources.size() == 1)
|
||||||
{
|
return resources.get(0);
|
||||||
URI uri = url.toURI();
|
|
||||||
return newResource(uri);
|
return combine(resources);
|
||||||
}
|
|
||||||
catch (URISyntaxException e)
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("Error creating resource from URL: " + url, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,10 +19,12 @@ import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
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.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.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.eclipse.jetty.toolchain.test.FS;
|
import org.eclipse.jetty.toolchain.test.FS;
|
||||||
|
@ -31,6 +33,7 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.junit.jupiter.api.Assumptions;
|
import org.junit.jupiter.api.Assumptions;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.condition.OS;
|
import org.junit.jupiter.api.condition.OS;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -43,6 +46,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
@ -61,13 +65,44 @@ public class ResourceFactoryTest
|
||||||
"TestData/alphabet.txt", "/TestData/alphabet.txt",
|
"TestData/alphabet.txt", "/TestData/alphabet.txt",
|
||||||
"TestData/", "/TestData/", "TestData", "/TestData"
|
"TestData/", "/TestData/", "TestData", "/TestData"
|
||||||
})
|
})
|
||||||
public void testNewClassLoaderResourceExists(String reference)
|
@Disabled
|
||||||
|
public void testNewClassLoaderResourceExists(String reference) throws IOException
|
||||||
{
|
{
|
||||||
|
Path alt = workDir.getEmptyPathDir().resolve("alt");
|
||||||
|
Files.createDirectories(alt.resolve("TestData"));
|
||||||
|
URI altURI = alt.toUri();
|
||||||
|
ClassLoader loader = new URLClassLoader(new URL[] {altURI.toURL()});
|
||||||
|
|
||||||
|
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
|
||||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||||
{
|
{
|
||||||
|
Resource altResource = resourceFactory.newResource(altURI);
|
||||||
|
|
||||||
|
Thread.currentThread().setContextClassLoader(loader);
|
||||||
Resource resource = resourceFactory.newClassLoaderResource(reference);
|
Resource resource = resourceFactory.newClassLoaderResource(reference);
|
||||||
assertNotNull(resource, "Reference [" + reference + "] should be found");
|
assertNotNull(resource, "Reference [" + reference + "] should be found");
|
||||||
assertTrue(resource.exists(), "Reference [" + reference + "] -> Resource[" + resource + "] should exist");
|
assertTrue(resource.exists(), "Reference [" + reference + "] -> Resource[" + resource + "] should exist");
|
||||||
|
|
||||||
|
if (resource.isDirectory())
|
||||||
|
{
|
||||||
|
assertThat(resource, instanceOf(CombinedResource.class));
|
||||||
|
AtomicBoolean fromWorkDir = new AtomicBoolean();
|
||||||
|
AtomicBoolean fromResources = new AtomicBoolean();
|
||||||
|
resource.forEach(r ->
|
||||||
|
{
|
||||||
|
if (r.isContainedIn(altResource))
|
||||||
|
fromWorkDir.set(true);
|
||||||
|
else
|
||||||
|
fromResources.set(true);
|
||||||
|
});
|
||||||
|
assertTrue(fromWorkDir.get());
|
||||||
|
assertTrue(fromResources.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Thread.currentThread().setContextClassLoader(oldLoader);
|
||||||
|
workDir.ensureEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue