Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.nio.file.FileSystemNotFoundException when creating a resource from a JAR URL #10274

Closed
ekupcik opened this issue Aug 9, 2023 · 7 comments · Fixed by #10276
Closed

java.nio.file.FileSystemNotFoundException when creating a resource from a JAR URL #10274

ekupcik opened this issue Aug 9, 2023 · 7 comments · Fixed by #10276
Assignees
Labels
Bug For general bugs on Jetty side Documentation

Comments

@ekupcik
Copy link

ekupcik commented Aug 9, 2023

Jetty version(s)
12

Java version/vendor (use: java -version)
17

OS type/version
Windows

Description
In my application we are using an embedded jetty that creates multiple web application contexts. The setup is completely custom and consists of collecting multiple JARs in a way so that some of the JARs are shared between the web application contexts while others belong to just one context. For this we are building individual resources using URIs like

jar:file:///C:/app/lib/some.jar!/META-INF/resources
jar:file:///C:/app/lib/other.jar!/META-INF/resources

This worked fine in Jetty 10 but I was not able to get this running when migrating to Jetty 12. There I was trying to build the resources using

ResourceFactory.getBestByScheme(loUri).newResource(loUri)

and but that crashes with a FileSystemNotFoundException. I also tried various other ways/URIs to create a resource for that JAR etc but they just returned null.

java.nio.file.FileSystemNotFoundException
	at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:156)
	at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:142)
	at org.eclipse.jetty.util.resource.MountedPathResourceFactory.newResource(MountedPathResourceFactory.java:42)
	at org.eclipse.jetty.util.resource.ResourceFactory.newResource(ResourceFactory.java:191)

After debugging into MountedPathResourceFactory that is returned by ResourceFactory.getBestByScheme(loUri) and the java.nio.file.FileSystems code I think that MountedPathResourceFactory has a bug regarding the FileSystems.

How to reproduce?

public class MountedPathResourceFactory implements ResourceFactory
{
	@Override
	public Resource newResource(URI uri)
	{
		try
		{
			FileSystems.getFileSystem(uri);
		}
		catch(FileSystemNotFoundException e)
		{
			try
			{
				FileSystems.newFileSystem(uri, Map.of("create", "true"));
			}
			catch (IOException e1)
			{
				e1.printStackTrace();
			}
		}

		Path path = Paths.get(uri.normalize());
		if (!Files.exists(path))
			return null;
		return new MountedPathResource(path, uri);
	}
}

The original code crashes with a FileSystemNotFoundException in Paths.get(uri.normalize()). With the hacked workaround the everything is fine. The resource is resource is created and it also the whole web application context works just fine. I am not familiar with the FileSystems API so I have no idea what is the really proper way to use them. But it looks like it is necessary to create them first explicitly before you can use them and I don't think this is something that the application itself should have to do.

@ekupcik ekupcik added the Bug For general bugs on Jetty side label Aug 9, 2023
@joakime
Copy link
Contributor

joakime commented Aug 9, 2023

Does C:/app/lib/other.jar exist when you use ResourceFactory.newResource(URI) ?

Unlike Jetty 11 (and earlier), where a resource is validated much later, on Jetty 12, a Resource is validated on first reference.
Make sure your JAR files exist before you attempt to reference them via a ResourceFactory.newResource(uri) call.

A JAR file can be referenced in 2 different ways.

As a file

Resource jarfile = ResourceFactory.root().newResource(URI.create("file://C:/app/lib/other.jar"));

This is Resource that references a file, and nothing using this Resource will be able to see the contents of this JAR file as anything but the raw bytes of the file. (eg: new FileInputStream(file))

As the contents of the jar-file

ResourceFactory resourceFactory = ResourceFactory.of(server);

URI jarURI = URI.create("file://C:/app/lib/other.jar");

// Direct manipulation of URI string
Resource jar1 = resourceFactory.newResource(URI.create("jar:" + jarURI.toASCIIString() + "!/"));

// Using newJarResource method
Resource jar2 = resourceFactory.newJarResource(jarURI);

// Using URIUtil technique to adjust URI first
Resource jar3 = resourceFactory.newResource(URIUtil.toJarFileUri(jarURI));

Each of these references result in a Resource that is a directory of the uncompressed contents of the JAR.

@joakime
Copy link
Contributor

joakime commented Aug 9, 2023

ResourceFactory.getBestByScheme(loUri).newResource(loUri)

The way to use a ResourceFactory is to use it via one of the systems that track their reference counts.

Tracking resources via a LifeCycle

This is the most common technique.

WebAppContext context = new WebAppContext();
ResourceFactory resourceFactory = ResourceFactory.of(context);
Resource resource = resourceFactory.newResource(uri);
context.setBaseResource(resource);

This will release the resource when the Jetty LifeCycle it is attached to is stopped (eg: WebAppContext, ServletContextHandler, or Server)

Tracking Resources via try-with-resources

try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
{
    Resource resource = resourceFactory.newResource(uri);
    // do something with resource
}

This is a common technique when you need a resource for a short time (to investigate content, unit testing, etc).

Tracking Resources via JVM

ResourceFactory resourceFactory = ResourceFactory.root();

Resource resource = resourceFactory.newResource(uri);

This technique should be used with caution, as the Resource will not be freed until the JVM stops.

@joakime joakime self-assigned this Aug 9, 2023
@ekupcik
Copy link
Author

ekupcik commented Aug 9, 2023

Ahh... It is alive!

ResourceFactory.of(loWebAppContext).newResource(loUri)

works like a charm. Thanks a lot. This has already cost me countless hours. I was assuming that you must be initializing the filesystems somewhere else. But nice that it is so close.

So this can be probaly closed unless you want to improve the error handling

@joakime
Copy link
Contributor

joakime commented Aug 9, 2023

@ekupcik good to hear.

You can use the various Jetty dump methods (see Server.dump* and WebAppContext.dump* methods) to see what resources are being tracked too (only works if you are using ResourceFactory.of(lifecycle))

@joakime
Copy link
Contributor

joakime commented Aug 9, 2023

Leaving this open, as I'll give an improvement to the Javadoc a whirl.

@joakime
Copy link
Contributor

joakime commented Aug 9, 2023

First pass at improved javadoc at PR #10276

@joakime joakime moved this to 🏗 In progress in Jetty 12.0.1 - FROZEN Aug 9, 2023
joakime added a commit that referenced this issue Aug 16, 2023
* Improve javadoc + move internal methods
@joakime
Copy link
Contributor

joakime commented Aug 28, 2023

Merged PR #10276 to address documentation concerns

@joakime joakime closed this as completed Aug 28, 2023
@joakime joakime moved this from 🏗 In progress to ✅ Done in Jetty 12.0.1 - FROZEN Aug 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug For general bugs on Jetty side Documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants