After I could eventually figure out why JWS 1.6.0_29 failed to launch a 1.4.2_12 application (see this question), I faced another exception when launching a 1.4.2_12 app. with JWS 1.6.0_29.
I get a MissingResourceException when loading a ResourceBundle. Yet a message.properties file do exists in the same package as the class that’s loading it.
When JWS 1.4 or 1.5 is used to launch the application, the exception is not raised.
The exception is raised only when launching the app. with JWS 1.6.
Full stackstrace is :
java.lang.ExceptionInInitializerError
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javaws.Launcher.executeApplication(Unknown Source)
at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
at com.sun.javaws.Launcher.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.util.MissingResourceException: Can't find bundle for base name com.test.hello.messages, locale fr_FR
at java.util.ResourceBundle.throwMissingResourceException(Unknown Source)
at java.util.ResourceBundle.getBundleImpl(Unknown Source)
at java.util.ResourceBundle.getBundle(Unknown Source)
at com.test.hello.Main.<clinit>(Main.java:10)
... 9 more
Test case to reproduce
JNLP descriptor is:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://localhost:80/meslegacy/apps" href="testJwsXXTo142.jnlp">
<information>
<title>JWS TEST 1.6 -> 1.4.2</title>
<vendor>Hello World Vendor</vendor>
<description>Hello World</description>
</information>
<security>
<all-permissions />
</security>
<resources>
<j2se version="1.4.2_12" href="http://java.sun.com/products/autodl/j2se" />
<jar href="jar/helloworld.jar" main="true" />
</resources>
<application-desc main-class="com.test.hello.Main" />
</jnlp>
com.test.hello.Main class is:
package com.test.hello;
import java.util.ResourceBundle;
import javax.swing.JFrame;
public class Main {
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(Main.class.getPackage().getName()+".messages");
public static void main(String[] args) {
JFrame frame = new JFrame("Hello world !");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
}
}
Complementary tests
- Specifying ClassLoader and Locale to the ResourceBundle.getBundle()
method does not fix the problem. - Main.class.getClassLaoder() and
Thread.currentThread().getContextClassLaoder() have been tested and spawn the same exception. - Loading resource “by hand” does work (see below).
Test code to load resource manually :
ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/");
System.out.println(resourcePath);
URL resourceUrl = cl.getResource(resourcePath+".properties");
System.out.println("Resource manually loaded :"+resourceUrl);
Will produce :
com/test/hello/messages.properties
Resource manually loaded :jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties
- However, while it is possible to find the resource, get the resource content is not.
Example:
ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/") + ".properties";
URL resourceUrl = cl.getResource(resourcePath);
// here, resourceUrl is not null. Then build bundle by hand
ResourceBundle prb = new PropertyResourceBundle(resourceUrl.openStream());
Which spawns :
java.io.FileNotFoundException: JAR entry com%2ftest%2fhello%2fmessages.properties not found in C:\Documents and Settings\firstname.lastname\Application Data\Sun\Java\Deployment\cache\6.0\18\3bfe5d92-3dfda9ef
at com.sun.jnlp.JNLPCachedJarURLConnection.connect(Unknown Source)
at com.sun.jnlp.JNLPCachedJarURLConnection.getInputStream(Unknown Source)
at java.net.URL.openStream(Unknown Source)
at com.test.hello.Main.main(Main.java:77)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javaws.Launcher.executeApplication(Unknown Source)
at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
at com.sun.javaws.Launcher.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Seems to be more a kind of cache issue…
I any of you had a hint, it would be greatly appreciated,
Thanks for reading.
Here is the explanation and workarround for this problem.
1 – Explanation
The problem comes from the URLs returned by the system ClassCloader (JWS6 system ClassLoader).
With JWS 1.6, URL returned by the system ClassLoader contain escape sequences such as the one shown in the following :
Locating resources in classpath is possible but when it comes to actually access the content of that resource a FileNotFoundException is raised: This is what causes the FileNotFoundException in ResourceBundle.
Please note that when no escape sequence appears in the URL, for example when the resource is at the root of the claspath, there is no problem to access the resource content. Problem appears only when you get %xx stuff in the URL path part.
2 – Workarround
Once the problem had been focused (it took me days to figure this out !), it was time to find a workarround for this.
While it would have been possible for me to fix my problem on specific localized code parts, it quickly turned out that is was possible to fix the issue globaly by coding a specific ClassLoader to “replace” the JNLPClassLoader.
I don’t acutally “replace” because it seems impossible to me but I rather do the following :
classloader
JNLPClassLoader
classloader
This gives the following ClassLoader
Now in a AppBoostrap class which I set to be the main-class in the JNLP descriptor, I do the following :
In the previous excerpt I get the ClassLoader that loaded my AppBootstrap class and use it as the parent classloader of my JwsUrlFixerClassLoader.
I had to fix the problem of the default parent delegation strategy of the URLClassLodaer.loadClass() and replace it with the “try my classpath first then parent”.
After that has been done everything went right and a couple of other bugs that we so far couldn’t explain have disapeared.
That’s magic ! After a lot of pain though…
Hope this can help someone one day…