org.eclipse.jetty.util.resource.ResourceFactoryInternals Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-util Show documentation
Show all versions of jetty-util Show documentation
Utility classes for Jetty
The newest version!
//
// ========================================================================
// Copyright (c) 1995 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.util.resource;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.ProviderNotFoundException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ResourceFactoryInternals
{
private static final Logger LOG = LoggerFactory.getLogger(ResourceFactoryInternals.class);
private static final Path CURRENT_WORKING_DIR;
/**
* The Index (Map) of URI schemes to ResourceFactory implementations that is used by {@link CompositeResourceFactory}
*/
static final Index.Mutable RESOURCE_FACTORIES = new Index.Builder()
.caseSensitive(false)
.mutable()
.build();
static
{
CURRENT_WORKING_DIR = Path.of(System.getProperty("user.dir"));
// The default resource factories
MountedPathResourceFactory mountedPathResourceFactory = new MountedPathResourceFactory();
RESOURCE_FACTORIES.put("jar", mountedPathResourceFactory);
PathResourceFactory pathResourceFactory = new PathResourceFactory();
RESOURCE_FACTORIES.put("file", pathResourceFactory);
RESOURCE_FACTORIES.put("jrt", pathResourceFactory);
/* Best-effort attempt to support an alternate FileSystem type that is in use for classpath
* resources.
*
* The build.properties is present in the jetty-util jar, and explicitly included for reflection
* with native-image (unlike classes, which are not accessible by default), so we use that
* resource as a reference.
*/
URL url = ResourceFactoryInternals.class.getResource("/org/eclipse/jetty/version/build.properties");
if ((url != null) && !RESOURCE_FACTORIES.contains(url.getProtocol()))
{
ResourceFactory resourceFactory;
if (GraalIssue5720PathResource.isAffectedURL(url))
{
resourceFactory = new GraalIssue5720PathResourceFactory();
}
else
{
resourceFactory = url.toString().contains("!/") ? mountedPathResourceFactory : pathResourceFactory;
}
RESOURCE_FACTORIES.put(url.getProtocol(), resourceFactory);
}
}
static ResourceFactory ROOT = new CompositeResourceFactory()
{
@Override
protected void onMounted(FileSystemPool.Mount mount, URI uri)
{
// Since this ROOT ResourceFactory and has no lifecycle that can clean up
// the mount, we shall report this mount as a leak
if (LOG.isDebugEnabled())
LOG.warn("Leaked {} for {}", mount, uri, new Throwable());
else
LOG.warn("Leaked {} for {}", mount, uri);
}
};
/**
* Test uri to know if a {@link ResourceFactory} is registered for it.
*
* @param uri the uri to test
* @return true if a ResourceFactory is registered to support the uri
* @see ResourceFactory#registerResourceFactory(String, ResourceFactory)
* @see ResourceFactory#unregisterResourceFactory(String)
* @see #isSupported(String)
*/
static boolean isSupported(URI uri) // TODO: boolean isSupported
{
if (uri == null || uri.getScheme() == null)
return false;
return RESOURCE_FACTORIES.get(uri.getScheme()) != null;
}
/**
* Test string to know if a {@link ResourceFactory} is registered for it.
*
* @param str the string representing the resource location
* @return true if a ResourceFactory is registered to support the string representation
* @see org.eclipse.jetty.util.Index#getBest(String)
* @see ResourceFactory#registerResourceFactory(String, ResourceFactory)
* @see ResourceFactory#unregisterResourceFactory(String)
* @see #isSupported(URI)
*/
static boolean isSupported(String str)
{
if (StringUtil.isBlank(str))
return false;
return RESOURCE_FACTORIES.getBest(str) != null;
}
interface Tracking
{
int getTrackingCount();
}
static class Closeable implements ResourceFactory.Closeable, Tracking
{
private boolean closed = false;
private final CompositeResourceFactory _compositeResourceFactory = new CompositeResourceFactory();
@Override
public Resource newResource(URI uri)
{
if (closed)
throw new IllegalStateException("Unable to create new Resource on closed ResourceFactory");
return _compositeResourceFactory.newResource(uri);
}
@Override
public void close()
{
closed = true;
for (FileSystemPool.Mount mount : _compositeResourceFactory.getMounts())
IO.close(mount);
_compositeResourceFactory.clearMounts();
}
public int getTrackingCount()
{
return _compositeResourceFactory.getMounts().size();
}
}
static class LifeCycle extends AbstractLifeCycle implements ResourceFactory.LifeCycle
{
private final CompositeResourceFactory _compositeResourceFactory = new CompositeResourceFactory();
@Override
public Resource newResource(URI uri)
{
// TODO: add check that LifeCycle is started before allowing this method to be used?
return _compositeResourceFactory.newResource(uri);
}
@Override
protected void doStop() throws Exception
{
for (FileSystemPool.Mount mount : _compositeResourceFactory.getMounts())
IO.close(mount);
_compositeResourceFactory.clearMounts();
super.doStop();
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
List referencedUris = _compositeResourceFactory.getMounts().stream()
.map(mount -> mount.root().getURI())
.toList();
Dumpable.dumpObjects(out, indent, this, new DumpableCollection("newResourceReferences", referencedUris));
}
}
static class CompositeResourceFactory implements ResourceFactory
{
private final List _mounts = new CopyOnWriteArrayList<>();
@Override
public Resource newResource(URI uri)
{
if (uri == null)
return null;
try
{
// If the URI is not absolute
if (!uri.isAbsolute())
{
// If it is an absolute path,
if (uri.toString().startsWith("/"))
// just add the scheme
uri = new URI("file", uri.toString(), null);
else
// otherwise resolve against the current directory
uri = CURRENT_WORKING_DIR.toUri().resolve(uri);
// Correct any mistakes like `file:/path` (to `file:///path`)
uri = URIUtil.correctURI(uri);
}
ResourceFactory resourceFactory = RESOURCE_FACTORIES.get(uri.getScheme());
if (resourceFactory == null)
throw new IllegalArgumentException("URI scheme not registered: " + uri.getScheme());
if (resourceFactory instanceof MountedPathResourceFactory)
{
FileSystemPool.Mount mount = mountIfNeeded(uri);
if (mount != null)
{
_mounts.add(mount);
onMounted(mount, uri);
}
}
return resourceFactory.newResource(uri);
}
catch (URISyntaxException | ProviderNotFoundException ex)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to create resource from: {}", uri, ex);
throw new IllegalArgumentException("Unable to create resource", ex);
}
}
/**
* Mount a URI if it is needed.
*
* @param uri The URI to mount that may require a FileSystem (e.g. "jar:file://tmp/some.jar!/directory/file.txt")
* @return A reference counted {@link FileSystemPool.Mount} for that file system or null. Callers should call
* {@link FileSystemPool.Mount#close()} once they no longer require this specific Mount.
* @throws IllegalArgumentException If the uri could not be mounted.
*/
private FileSystemPool.Mount mountIfNeeded(URI uri)
{
// do not mount if it is not a jar URI
String scheme = uri.getScheme();
if (!"jar".equalsIgnoreCase(scheme))
return null;
// Do not mount if we have already mounted
// TODO there is probably a better way of doing this other than string comparisons
String uriString = uri.toASCIIString();
for (FileSystemPool.Mount mount : _mounts)
{
if (uriString.startsWith(mount.root().toString()))
return null;
}
try
{
return FileSystemPool.INSTANCE.mount(uri);
}
catch (IOException ioe)
{
throw new IllegalArgumentException("Unable to mount: " + uri, ioe);
}
}
protected void onMounted(FileSystemPool.Mount mount, URI uri)
{
// override to specify behavior
}
public List getMounts()
{
return _mounts;
}
public void clearMounts()
{
_mounts.clear();
}
}
}