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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
@ -20,11 +21,11 @@ import java.nio.file.InvalidPathException;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
@ -197,7 +198,7 @@ public interface ResourceFactory
|
|||
*
|
||||
* @param resource the resource name to find in a classloader
|
||||
* @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.
|
||||
*/
|
||||
default Resource newClassLoaderResource(String resource, boolean searchSystemClassLoader)
|
||||
|
@ -205,47 +206,60 @@ public interface ResourceFactory
|
|||
if (StringUtil.isBlank(resource))
|
||||
throw new IllegalArgumentException("Resource String is invalid: " + resource);
|
||||
|
||||
URL url = null;
|
||||
|
||||
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)
|
||||
// We need a local interface to combine static and non-static methods
|
||||
interface Source
|
||||
{
|
||||
if (url != null)
|
||||
break;
|
||||
Enumeration<URL> getResources(String name) throws IOException;
|
||||
}
|
||||
|
||||
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);
|
||||
if (url == null && resource.startsWith("/"))
|
||||
url = loader.apply(resource.substring(1));
|
||||
}
|
||||
catch (IllegalArgumentException 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);
|
||||
try
|
||||
{
|
||||
// Get all matching URLs
|
||||
Enumeration<URL> urls = source.getResources(name);
|
||||
while (urls.hasMoreElements())
|
||||
{
|
||||
// Get the resource
|
||||
Resource r = newResource(urls.nextElement().toURI());
|
||||
// If it is not a directory, then return it as the singular found resource
|
||||
if (!r.isDirectory())
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = url.toURI();
|
||||
return newResource(uri);
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
throw new IllegalArgumentException("Error creating resource from URL: " + url, e);
|
||||
}
|
||||
if (resources.size() == 1)
|
||||
return resources.get(0);
|
||||
|
||||
return combine(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,10 +19,12 @@ import java.net.HttpURLConnection;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.util.URIUtil;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
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.containsString;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -61,13 +65,44 @@ public class ResourceFactoryTest
|
|||
"TestData/alphabet.txt", "/TestData/alphabet.txt",
|
||||
"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())
|
||||
{
|
||||
Resource altResource = resourceFactory.newResource(altURI);
|
||||
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
Resource resource = resourceFactory.newClassLoaderResource(reference);
|
||||
assertNotNull(resource, "Reference [" + reference + "] should be found");
|
||||
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