Workaround for bug id 6753651 – find path to jar in cache under webstart

Here is a workaround for the changes in 1.5.0_16 and 1.6.0_07, which prevent you from obtaining a valid URL using Class.getResource(myClassName) under webstart.

This is related to bug id 6753651 raised here

URLs acquired in this way used to include a filesystem path to the jar file in the local webstart cache containing the resource. Some apps relied on this filesystem path.

The behaviour of Class.getResource() under webstart was changed in 1.5.0_16 and 1.6.0_07 due to a security patch.

  • In 1.5.0_16 this breaks the URL (i.e. the URL returned by url.toString() is no longer valid, the file path is removed, which breaks some apps).
  • In 1.6.0_07 the url now uses the http protocol to refer to the jar file on the server – although in this case under the covers webstart will actually use the jar in the local cache if you call URL.openConnection()

One of our apps relied on the file path information in the URL, so we were forced to find a workaround.

This workaround does not fix the broken URL – but it does at least let you use the URL to find a valid path to the jar file on the local filesystem

update Nov 28th 08 – if you want to get a valid URL you might check out David’s suggestions in the comments below this post

To use the workaround first call Class.getResource(myResource) to get a (broken) URL , and then pass that URL into getJarFilePath(URL url). You should get back the path to the jar file in the local webstart cache which contains the resource.

It should work for all JDK/webstart versions up to and including 1.5.0_16 and 1.6.0_07 (subsequent versions have not been tested)

This workaround will only work for signed apps running with all permissions, since it uses reflection to access private methods and fields in classes within webstart.jar.

This workaround is pretty horrid, it certainly feels sinful, but it is the best solution I can find at present, having to fix a mission critical application within the next 24 hours. Since it relies on private implementation details within the webstart jar it may break with future jre versions if that implementation is changed. It is not part of the public API, so changes made in subsequent jdks may break the workaround at any time.

n.b. you may need to call System.setSecurityManager(null) before you call this routine to allow the private field access to work.

     /**
     * This method will return the path to the jar file containing the resource which this URL references
     *
     * It should work with URLs returned by class.getResource() under java 1.5.0_16
     * and 1.6.0_07, as well as maintaining backwards compatibility with previous jres
     *
     * The two jre above contain security patches which make the file path of the jar
     * inaccessible under webstart. This patch works around that by using reflection to access
     * private fields in the webstart.jar where required. This will only work for signed webstart
     * apps running with all security permissions
     *
     * @param jarUrl - url which has jar as the protocol
     * @return path to Jar file for this jarURL
     */

    public static String getJarFilePath(URL jarUrl) {
        JarFile jarFile = getJarFile(jarUrl);
        return findJarPath(jarFile);
    }

    public static JarFile getJarFile(URL jarUrl) {
        try {
            JarURLConnection jarUrlConnection = (JarURLConnection)jarUrl.openConnection();

            //try the getJarFile method first.
            //Under webstart in 1.5.0_16 this is overriden to return null
            JarFile jarFile = jarUrlConnection.getJarFile();

            if ( jarFile == null) {
                jarFile = getJarFileByReflection(jarUrlConnection);
            }
            return jarFile;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to get JarFile from jarUrlConnection", t);
        }
    }

    private static JarFile getJarFileByReflection(JarURLConnection jarUrlConnection) throws Exception {
        //this class only exists in webstart.jar for 1.5.0_16 and later
        Class jnlpConnectionClass = Class.forName("com.sun.jnlp.JNLPCachedJarURLConnection");
        Field jarFileField;
        try {
            jarFileField = jnlpConnectionClass.getDeclaredField("jarFile");
        } catch ( Throwable t) {
            jarFileField = jnlpConnectionClass.getDeclaredField("_jarFile");
        }
        jarUrlConnection.connect(); //this causes the connection to set the jarFile field
        jarFileField.setAccessible(true);
        return (JarFile)jarFileField.get(jarUrlConnection);
    }

    private static String findJarPath(JarFile cachedJarFile) {
        try {
            String name = cachedJarFile.getName();

            //getName is overridden to return "" under 1.6.0_7 so use reflection
            if ( name == null || name.trim().equals("")) {
                Class c = ZipFile.class;
                Field field = c.getDeclaredField("name");
                field.setAccessible(true);
                name = (String)field.get(cachedJarFile);
            }
            return name;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to get find name from jarFile", t);
        }
    }

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

AddThis Social Bookmark Button

5 Responses to “Workaround for bug id 6753651 – find path to jar in cache under webstart”

  1. David Ehrlich Says:

    While not returning exactly the same result as what you suggest, the following code seems to address the JNLP issue Sun introduced with JDK 1.5.0_16:

        public static URL getResource(Class clazz, String name) {
            // Get the URL for the resource using the standard behavior
            URL result = clazz.getResource(name);

            // Check to see that the URL is not null and that it's a JAR URL.
            if (result != null && "jar".equalsIgnoreCase(result.getProtocol())) {
                // Get the URL to the "clazz" itself.  In a JNLP environment, the "getProtectionDomain" call should succeed only with properly signed JARs.
                URL classSourceLocationURL = clazz.getProtectionDomain().getCodeSource().getLocation();
                // Create a String which embeds the classSourceLocationURL in a JAR URL referencing the desired resource.
                String urlString = MessageFormat.format("jar:{0}!/{1}/{2}", classSourceLocationURL.toExternalForm(), packageNameOfClass(clazz).replaceAll("\.", "/"), name);

                // Check to see that new URL differs.  There's no reason to instantiate a new URL if the external forms are identical (as happens on pre-1.5.0_16 builds of the JDK).
                if (urlString.equals(result.toExternalForm()) == false) {
                    // The URLs are different, try instantiating the new URL.
                    try {
                        result = new URL(urlString);
                    } catch (MalformedURLException malformedURLException) {
                        throw new RuntimeException(malformedURLException);
                    }
                }
            }
            return result;
        }

        public static String packageNameOfClass(Class clazz) {
            String result = "";
            String className = clazz.getName();
            int lastPeriod = className.lastIndexOf(".");

            if (lastPeriod > -1) {
                result = className.substring(0, lastPeriod);
            }
            return result;
        }
  2. thanks for that code David, that looks like another approach worth investigating. Does it also work with 1.6.0_07 I wonder?

  3. Hi,
    Thanks for your code, I think it can help me but I can´t see how, because I´m not an java expert.
    I had
    URL myUrl = this.getClass().getResource(“Pant.class”);
    String urlString=URLDecoder.decode(myUrl.toString(),”UTF8″);

    To replace this with your code should I use now
    URL myUrl= this.getClass().getResource(“Pant.class”);
    String urlString=getJarFilePath(myUrl);
    ??

    Thanks

  4. sorry I mean
    urlString=URLDecoder.decode(getJarFilePath(myUrl),”UTF8″);

  5. Looks about right John, the getJarFilePath() should give you the path to the jar file downloaded into the webstart cache.

    However it would be worth checking out David’s solution above if you need the path to the jar. I haven’t yet validated this myself, but in theory it uses a public API to get the same information. My solution is a rather nasty workaround to get this same info – it does work fine currently, but since it uses reflection rather than a public API it will be more prone to break in future versions of webstart/the jdk. It uses parts of the webstart library which are not public. If David’s getProtectionDomain() method works it should be less likely to break in the future in this way.

Leave a Reply