It looks like there are problems using Jimfs in an environment where user and system code are loaded by separate classloaders. In that case, JimfsFileSystemProvider will be loaded and initialized by the system classloader (because of the META-INF/services entry), while a user who wants to create a file system by calling Jimfs.newFileSystem will be using classes loaded by a different classloader.
The problem is that to create a FileSystem instance, Jimfs calls FileSystems.newFileSystem(...), passing it an environment map containing the Configuration object to be used to configure the file system. When the JimfsFileSystemProvider instance loaded by the system classloader gets this object, it doesn't recognize it as an instance of Configuration, because it was loaded by a different classloader. This leads to an error like:
env map ({config=com.google.common.jimfs.Configuration@15b71125}) must contain key 'config' mapped to an instance of Jimfs.Configuration
Why do we do it this way in the first place? We want to call FileSystems.newFileSystem so that the canonical (system-loaded) JimfsFileSystemProvider instance is used to create (and more importantly, cache) the file system. This is necessary for methods like Paths.get(URI) to work, as it will go to that FileSystemProvider instance and ask it to resolve the URI to a Path. If it doesn't have the FileSystem instance cached, it won't be able to find it and get a Path from it.
Some possible solutions (and the problems with them):
- Change the environment map that's passed to
FileSystems.newFileSystem to only contain standard JDK types. This should solve the classloader issues.
- Problem: While most of the
Configuration object could be converted to a map of standard JDK types, there are a couple things that cannot, specifically the PathType and the set of AttributeProviders the file system should use. It's possible (though it would require changing the API) that we could change the configuration from storing instances of these types to storing only the classes (requiring a default constructor or some such), in which case we could put the names of the classes into the map to be loaded and instantiated by the FileSystemProvider.
- Even if we did this, though, there are still potential problems if, say, an
AttributeProvider deals with attributes whose values are not standard JDK classes--the could be problems when a user tries to set an attribute value on a file and the AttributeProvider doesn't see it as the same class (due to the classloader issues).
- Stop using
FileSystems.newFileSystem to create Jimfs file systems. Use a locally initialized instance of JimfsFileSystemProvider instead. This solves the problem because only the user code classloader should be involved.
- Problem: It's no longer possible to get a Jimfs
Path or FileSystem by its URI using the standard methods in the java.nio.file API. This also prevents things like the recently-added support for Jimfs URLs from working.
- Try to work around this by making the system-loaded
FileSystemProvider for Jimfs not be the real JimfsFileSystemProvider.
- Rather, it would just be used for caching
FileSystem instances as needed for URI-based methods to work.
- The real
JimfsFileSystemProvider would be a singleton, and Jimfs would use that to create a FileSystem instance when Jimfs.newFileSystem is called. It would then pass the created FileSystem itself as a value in the environment map to FileSystems.newFileSystem, allowing the new FileSystem to be cached. Since FileSystem is a JDK type and since the system-loaded FileSystemProvider has no reason to care about any details beyond that, it should have no problem with the FileSystem coming from a different classloader.
- Since the
FileSystemProvider that created the FileSystem will be the one that handles implementing all the methods for it, there should be no issues with the FileSystem functionality.
I think approach 3 should work, but still need to confirm that. It's fairly gross, but should be transparent as long as people are using the API as expected.
It looks like there are problems using Jimfs in an environment where user and system code are loaded by separate classloaders. In that case,
JimfsFileSystemProviderwill be loaded and initialized by the system classloader (because of theMETA-INF/servicesentry), while a user who wants to create a file system by callingJimfs.newFileSystemwill be using classes loaded by a different classloader.The problem is that to create a
FileSysteminstance,JimfscallsFileSystems.newFileSystem(...), passing it an environment map containing theConfigurationobject to be used to configure the file system. When theJimfsFileSystemProviderinstance loaded by the system classloader gets this object, it doesn't recognize it as an instance ofConfiguration, because it was loaded by a different classloader. This leads to an error like:Why do we do it this way in the first place? We want to call
FileSystems.newFileSystemso that the canonical (system-loaded)JimfsFileSystemProviderinstance is used to create (and more importantly, cache) the file system. This is necessary for methods likePaths.get(URI)to work, as it will go to thatFileSystemProviderinstance and ask it to resolve theURIto aPath. If it doesn't have theFileSysteminstance cached, it won't be able to find it and get aPathfrom it.Some possible solutions (and the problems with them):
FileSystems.newFileSystemto only contain standard JDK types. This should solve the classloader issues.Configurationobject could be converted to a map of standard JDK types, there are a couple things that cannot, specifically thePathTypeand the set ofAttributeProviders the file system should use. It's possible (though it would require changing the API) that we could change the configuration from storing instances of these types to storing only the classes (requiring a default constructor or some such), in which case we could put the names of the classes into the map to be loaded and instantiated by theFileSystemProvider.AttributeProviderdeals with attributes whose values are not standard JDK classes--the could be problems when a user tries to set an attribute value on a file and theAttributeProviderdoesn't see it as the same class (due to the classloader issues).FileSystems.newFileSystemto create Jimfs file systems. Use a locally initialized instance ofJimfsFileSystemProviderinstead. This solves the problem because only the user code classloader should be involved.PathorFileSystemby itsURIusing the standard methods in thejava.nio.fileAPI. This also prevents things like the recently-added support for JimfsURLs from working.FileSystemProviderfor Jimfs not be the realJimfsFileSystemProvider.FileSysteminstances as needed forURI-based methods to work.JimfsFileSystemProviderwould be a singleton, andJimfswould use that to create aFileSysteminstance whenJimfs.newFileSystemis called. It would then pass the createdFileSystemitself as a value in the environment map toFileSystems.newFileSystem, allowing the newFileSystemto be cached. SinceFileSystemis a JDK type and since the system-loadedFileSystemProviderhas no reason to care about any details beyond that, it should have no problem with theFileSystemcoming from a different classloader.FileSystemProviderthat created theFileSystemwill be the one that handles implementing all the methods for it, there should be no issues with theFileSystemfunctionality.I think approach 3 should work, but still need to confirm that. It's fairly gross, but should be transparent as long as people are using the API as expected.