2012年12月4日火曜日

Custom URLStreamHandler

First up, you're going to need at least a URLStreamHandler. This will actually open the connection to a given URL. Notice that this is simply called Handler; this allows you to specify java -Djava.protocol.handler.pkgs=org.my.protocols and it will automatically be picked up, using the "simple" package name as the supported protocol (in this case "classpath").

Usage

new URL("classpath:org/my/package/resource.extension").openConnection();
Code

package org.my.protocols.classpath;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

/** A {@link URLStreamHandler} that handles resources on the classpath. */
public class Handler extends URLStreamHandler {
/** The classloader to find resources from. */
private final ClassLoader classLoader;

public Handler() {
this.classLoader = getClass().getClassLoader();
}

public Handler(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
protected URLConnection openConnection(URL u) throws IOException {
final URL resourceUrl = classLoader.getResource(u.getPath());
return resourceUrl.openConnection();
}
}
Launch issues

If you're anything like me, you don't want to rely on a property being set in the launch to get you somewhere (in my case, I like to keep my options open like Java WebStart - which is why I need all this).
Workarounds/Enhancements

Manual code Handler specification

If you control the code, you can do

new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))
and this will use your handler to open the connection.

But again, this is less than satisfactory, as you don't need a URL to do this - you want to do this because some lib you can't (or don't want to) control wants urls...

JVM Handler registration

The ultimate option is to register a URLStreamHandlerFactory that will handle all urls across the jvm:

package my.org.url;

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;

class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory {
private final Map<String, URLStreamHandler> protocolHandlers;

public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) {
protocolHandlers = new HashMap<String, URLStreamHandler>();
addHandler(protocol, urlHandler);
}

public void addHandler(String protocol, URLStreamHandler urlHandler) {
protocolHandlers.put(protocol, urlHandler);
}

public URLStreamHandler createURLStreamHandler(String protocol) {
return protocolHandlers.get(protocol);
}
}
To register the handler, call URL.setURLStreamHandlerFactory() with your configured factory. Then do new URL("classpath:org/my/package/resource.extension") like the first example and away you go.

JVM Handler Registration Issue

Note that this method may only be called once per JVM, and note well that Tomcat will use this method to register a JNDI handler (AFAIK). Try Jetty (I will be); at worst, you can use the method first and then it has to work around you!

0 件のコメント:

コメントを投稿