Mark Sinke - 2013-09-20

(Note that this post relates to 0.97.1 - I don't know what the situation in 0.98 is.)

We have a similar problem with our code, for example in resolution of XSLT includes. The way we "fix"/work around it is by meddling with JAR URL loading.

We put a custom JAR URL Connection handler in using

/**
 * Groups the one-jar context-related functionality in one place.
 */
public final class OneJarContext {
    static final String PROTOCOL_HANDLER_PROPERTY = "java.protocol.handler.pkgs";

    private static boolean isOneJarAvailable = false;

    static {
        OneJarContext.setOneJarAvailable(isOneJarProtocolHandlerAvailable());
        URL.setURLStreamHandlerFactory(new OneJarAwareUrlStreamHandlerFactory());
    }

    static void setOneJarAvailable(boolean available) {
        OneJarContext.isOneJarAvailable = available;
    }

    static boolean isOneJarAvailable() {
        return isOneJarAvailable;
    }

    static boolean isOneJarProtocolHandlerAvailable() {
        String originalValue = System.getProperty(PROTOCOL_HANDLER_PROPERTY);
        Set<String> packages =
                new LinkedHashSet<>(Arrays.asList(emptyIfNull(originalValue).split("\\|")));
        packages.add("com.simontuffs");
        System.setProperty(PROTOCOL_HANDLER_PROPERTY, StringUtil.join(packages, "|"));

        try {
            new URL("onejar:dummy");
            return true;
        } catch (MalformedURLException e) {
            return false;
        }
    }

    /**
     * Initialize the one-jar context.
     */
    public static void init() {
        // no operation - calling this method enforces running the static initializer, which in
        // turn
        // makes sure the URLStreamHandlerFactory is only set once.
    }

    private OneJarContext() {
        // avoid instantiation
    }
}

(There is some magic going on with the PROTOCOL_HANDLER_PROPERTY and the static isOneJarAvailable member that relates to how we test this code, but you get the idea.)

The OneJarAwareUrlStreamHandlerFactory looks like this (I admit that it is not perfect, but it works for us):

/**
 * URL stream handler factory that knows about one-jar.
 */
class OneJarAwareUrlStreamHandlerFactory implements URLStreamHandlerFactory {
    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        if (!OneJarContext.isOneJarAvailable() || !"jar".equals(protocol)) {
            return null;
        }

        return new URLStreamHandler() {
            @Override
            protected URLConnection openConnection(URL u) throws IOException {
                URL newUrl = new URL("onejar", null, u.getPort(), removeJarPrefixes(u.getFile()));
                return newUrl.openConnection();
            }

            private String removeJarPrefixes(String path) {
                return path.replaceFirst(".*!", "");
            }
        };
    }
}

We basically turn every JAR URL into a onejar: URL (that is why we need part of the protocol handler initialization in OneJarContext), i.e., we force a classpath "search" of the code.

We had lots of other workarounds in place before, but made these superfluous.