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
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);
}
}
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
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.
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 :
jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties
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.
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 :
This gives the following ClassLoader
public class JwsUrlFixerClassLoader extends URLClassLoader {
private final static Logger LOG = Logger.getLogger(JwsUrlFixerClassLoader.class);
private static String SIMPLE_CLASS_NAME = null;
private static boolean LOG_ENABLED = "true".equals(System.getProperty("classloader.debug"));
static {
SIMPLE_CLASS_NAME = JwsUrlFixerClassLoader.class.getName();
int idx = SIMPLE_CLASS_NAME.lastIndexOf('.');
if (idx >= 0 && idx < SIMPLE_CLASS_NAME.length()-1) {
SIMPLE_CLASS_NAME = SIMPLE_CLASS_NAME.substring(idx + 1);
}
}
public JwsUrlFixerClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public URL getResource(String name) {
if (LOG.isDebugEnabled()) {
LOG.debug("getResource(): getResource(" + name + ")");
}
if (LOG_ENABLED) {
login("getResource(" + name + ")");
}
URL out = super.getResource(name);
if (out != null) {
out = URLFixerTool.fixUrl(out);
}
if (LOG_ENABLED) {
logout("getResource returning " + out);
}
return out;
}
public URL findResource(String name) {
if (LOG_ENABLED) {
login("findResource(" + name + ")");
}
URL out = super.findResource(name);
if (out != null) {
out = URLFixerTool.fixUrl(out);
}
if (LOG_ENABLED) {
logout("findResource returning " + out);
}
return out;
}
public InputStream getResourceAsStream(String name) {
if (LOG_ENABLED) {
login("getResourceAsStream(" + name + ")");
}
InputStream out = super.getResourceAsStream(name);
if (LOG_ENABLED) {
logout("getResourceAsStream returning " + out);
}
return out;
}
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (LOG_ENABLED) {
login("loadClass(" + name + ")");
}
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (ClassNotFoundException cnfe) {
if (getParent() == null) {
// c = findBootstrapClass0(name);
Method m = null;
try {
m = URLClassLoader.class.getMethod("findBootstrapClass0", new Class[] {});
m.setAccessible(true);
c = (Class) m.invoke(this, new Object[] { name });
} catch (Exception e) {
throw new ClassNotFoundException();
}
} else {
c = getParent().loadClass(name);
}
}
}
if (resolve) {
resolveClass(c);
}
if (LOG_ENABLED) {
logout("loadClass returning " + c);
}
return c;
}
private static void login(String message) {
System.out.println("---> [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
}
private static void logout(String message) {
System.out.println("<--- [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
}
}
Now in a AppBoostrap class which I set to be the main-class in the JNLP descriptor, I do the following :
System.setSecurityManager(null);
ClassLoader parentCL = AppBootstrap.class.getClassLoader();
URL[] classpath = new URL[] {};
if (parentCL instanceof URLClassLoader) {
URLClassLoader ucl = (URLClassLoader) parentCL;
classpath = ucl.getURLs();
}
final JwsUrlFixerClassLoader vlrCL = new JwsUrlFixerClassLoader(classpath, parentCL);
Thread.currentThread().setContextClassLoader(vlrCL);
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
Thread.currentThread().setContextClassLoader(vlrCL);
}
});
} catch (Exception e) {
LOG.error("main(): Failed to set context classloader !", e);
}
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...