Java NIO2 and the Groovy ClassLoader conundrum

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Java NIO2 and the Groovy ClassLoader conundrum

ysb33r

HI all,

Java NIO2 providers (https://docs.oracle.com/javase/8/docs/api/java/nio/file/spi/FileSystemProvider.html) works by providing filesystems via SPI. Ideally one would just write the appropriate classes and drop them on the classpath . This allows somebody does to write something like:

    Paths.get('gz:/path/to/compressed.gz')

This all works fine when done within a normal application but it fails when used within GroovyConsole or just running a normal Groovy script. ONe would expect the following just to work

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

Paths.get('gz:/path/to/compressed.gz')

However the following workaround is required

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

FileSystem fs

try {
    fs = FileSystems.getFileSystem(gzURI)
} catch(FileSystemNotFoundException) {
    fs = FileSystems.newFileSystem(gzURI,[:],this.class.classLoader.parent.parent)
}

Path gzPath = fs.provider().getPath(gzURI)

NOTE: Using @GrabConfig(initContextClassLoader=true,systemClassLoader=true) does not solve the problem in this case


At Gr8Conf I sat with with Andres, Jochen and Guillaume to work through this. The issue that that providers are loaded via the system class loader, whereas with GroovyConsole and command-line Groovy it will be loaded via an instance of RootLoader. This leads to one having to manually loading the filesystem.

From the point of view of someone using NIO2 providers this is surprising behaviour. One would expect it just to work out of the box (as per the Javadoc for FileSystemProvider).

My question is now. Is there something that can be done to way GroovyConsole and Groovy scripts are started so that the expectd behaviour of NIO2 providers can be obtained? (Maybe adding an additional method to GrabConfig?).

Groovy luck.

-- 
Schalk W. Cronjé
Twitter / Ello / Toeter : @ysb33r
Reply | Threaded
Open this post in threaded view
|

Re: Java NIO2 and the Groovy ClassLoader conundrum

Paolo Di Tommaso
The NIO api can be very annoying.

Hover I think here the problem is that Paths.get(<string path>) is designed to only return default file (local) system path objects. 

Instead Paths.get(<URI>) allows the automatic creation of a new file system as stated by the documentation

Throws 
FileSystemNotFoundException - The file system, identified by the URI, does not exist and cannot be created *automatically*, or the provider identified by the URI's scheme component is not installed


However is role of FileSystemProvider implementation to automatically create a new file system if does not exist and, I think, the ZipFileSystemProvider is not doing that. 



 

On Thu, May 31, 2018 at 8:53 AM, Schalk Cronjé <[hidden email]> wrote:

HI all,

Java NIO2 providers (https://docs.oracle.com/javase/8/docs/api/java/nio/file/spi/FileSystemProvider.html) works by providing filesystems via SPI. Ideally one would just write the appropriate classes and drop them on the classpath . This allows somebody does to write something like:

    Paths.get('gz:/path/to/compressed.gz')

This all works fine when done within a normal application but it fails when used within GroovyConsole or just running a normal Groovy script. ONe would expect the following just to work

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

Paths.get('gz:/path/to/compressed.gz')

However the following workaround is required

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

FileSystem fs

try {
    fs = FileSystems.getFileSystem(gzURI)
} catch(FileSystemNotFoundException) {
    fs = FileSystems.newFileSystem(gzURI,[:],this.class.classLoader.parent.parent)
}

Path gzPath = fs.provider().getPath(gzURI)

NOTE: Using @GrabConfig(initContextClassLoader=true,systemClassLoader=true) does not solve the problem in this case


At Gr8Conf I sat with with Andres, Jochen and Guillaume to work through this. The issue that that providers are loaded via the system class loader, whereas with GroovyConsole and command-line Groovy it will be loaded via an instance of RootLoader. This leads to one having to manually loading the filesystem.

From the point of view of someone using NIO2 providers this is surprising behaviour. One would expect it just to work out of the box (as per the Javadoc for FileSystemProvider).

My question is now. Is there something that can be done to way GroovyConsole and Groovy scripts are started so that the expectd behaviour of NIO2 providers can be obtained? (Maybe adding an additional method to GrabConfig?).

Groovy luck.

-- 
Schalk W. Cronjé
Twitter / Ello / Toeter : @ysb33r

Reply | Threaded
Open this post in threaded view
|

Re: Java NIO2 and the Groovy ClassLoader conundrum

ysb33r
On 31/05/2018 09:26, Paolo Di Tommaso wrote:
The NIO api can be very annoying.

Hover I think here the problem is that Paths.get(<string path>) is designed to only return default file (local) system path objects. 

Instead Paths.get(<URI>) allows the automatic creation of a new file system as stated by the documentation

Throws 
FileSystemNotFoundException - The file system, identified by the URI, does not exist and cannot be created *automatically*, or the provider identified by the URI's scheme component is not installed


However is role of FileSystemProvider implementation to automatically create a new file system if does not exist and, I think, the ZipFileSystemProvider is not doing that. 

You are correct. It is my typo in the code and a common mistake that is made. The code should read:

Paths.get('gz:/path/to/compressed.gz'.toURI())


-- 
Schalk W. Cronjé
Twitter / Ello / Toeter : @ysb33r
Reply | Threaded
Open this post in threaded view
|

Re: Java NIO2 and the Groovy ClassLoader conundrum

ysb33r
In reply to this post by ysb33r
Actually I found an issue in the example which incorrectly make it point to RootLoader. A better example is this

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

URI gzURI = "gz:/path/to/compressed.gz".toURI()

FileSystem fs

def cl = this.class.classLoader
while( fs == null && cl != null ) {
    println "Trying ${cl}"
    try {
        fs = FileSystems.getFileSystem(gzURI)
    } catch(FileSystemNotFoundException) {
        fs = FileSystems.newFileSystem(gzURI,[:],cl)
    }
    cl = cl.parent
}

println "Loaded filesystem '${fs}' using ${cl}"
Path gzPath = fs.provider().getPath(gzURI)
This produces the following output
Trying groovy.lang.GroovyClassLoader$InnerLoader@38831718
Loaded filesystem 'org.ysb33r.nio.provider.gz.GzFileSystem@615091b8' using groovy.lang.GroovyClassLoader@481a996b
which shows that GroovyClassLoader can be used to load the FileSystemProvider whereas the

On 31/05/2018 08:53, Schalk Cronjé wrote:

HI all,

Java NIO2 providers (https://docs.oracle.com/javase/8/docs/api/java/nio/file/spi/FileSystemProvider.html) works by providing filesystems via SPI. Ideally one would just write the appropriate classes and drop them on the classpath . This allows somebody does to write something like:

    Paths.get('gz:/path/to/compressed.gz')

This all works fine when done within a normal application but it fails when used within GroovyConsole or just running a normal Groovy script. ONe would expect the following just to work

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

Paths.get('gz:/path/to/compressed.gz')

However the following workaround is required

@Grab('org.ysb33r.nio:nio-gz-provider-commons:0.1')
@Grab('org.slf4j:slf4j-simple:1.7.5')

import java.nio.file.*

FileSystem fs

try {
    fs = FileSystems.getFileSystem(gzURI)
} catch(FileSystemNotFoundException) {
    fs = FileSystems.newFileSystem(gzURI,[:],this.class.classLoader.parent.parent)
}

Path gzPath = fs.provider().getPath(gzURI)

NOTE: Using @GrabConfig(initContextClassLoader=true,systemClassLoader=true) does not solve the problem in this case


At Gr8Conf I sat with with Andres, Jochen and Guillaume to work through this. The issue that that providers are loaded via the system class loader, whereas with GroovyConsole and command-line Groovy it will be loaded via an instance of RootLoader. This leads to one having to manually loading the filesystem.

From the point of view of someone using NIO2 providers this is surprising behaviour. One would expect it just to work out of the box (as per the Javadoc for FileSystemProvider).

My question is now. Is there something that can be done to way GroovyConsole and Groovy scripts are started so that the expectd behaviour of NIO2 providers can be obtained? (Maybe adding an additional method to GrabConfig?).

Groovy luck.

-- 
Schalk W. Cronjé
Twitter / Ello / Toeter : @ysb33r


-- 
Schalk W. Cronjé
Twitter / Ello / Toeter : @ysb33r