I'm having some issues wrapping my head around the concept of classloading, I've been programming for a little while now but I'm relatively knew to how classloading works, I've gone through a couple of examples and read about the details behind classloading and classes themselves, while I understand it to a certain extent theres a concept thats escaping me and seems rather hard to put into search-friendly terms.
Essentially, I'm trying to create 'guilds' for a gamemode I've been developing for Minecraft, these guilds lie in their own classes and are loaded up with the game at startup or whenever the method 'reloadGuildFiles()' is issued. I develop these classes by first exporting the main application and adding it to the classpath of the guild being created as well as the main applications dependencies.
Here is the 'reloadGuildFiles' method.
public void reloadGuildFiles() {
unloadGuildFiles();
synchronized ( _sync ) {
System.out.println( "Loading guild class files." );
File guildDataSourceDirectory = new File( "Prospect/Guilds/" );
URLClassLoader urlcl = null;
try {
urlcl = URLClassLoader.newInstance( new URL[] { guildDataSourceDirectory.toURI().toURL() }, Thread.currentThread().getContextClassLoader() );
} catch ( Exception e ) {
e.printStackTrace();
return;
}
if ( urlcl == null )
return;
for ( File guildDataFile : guildDataSourceDirectory.listFiles() ) {
if ( !guildDataFile.getName().endsWith( ".class" ) ) {
System.out.println( "Skipping " + guildDataFile.getName() );
continue;
}
try {
String className = guildDataFile.getName().substring( 0, guildDataFile.getName().lastIndexOf( "." ) );
System.out.println( "Loading: " + className + "\n" +
"\tfrom: " + guildDataFile.getPath() );
Class<?> clazz = urlcl.loadClass( className );
Object object = clazz.newInstance();
if ( object instanceof Guild == false ) {
System.out.println( "Object loaded is not an instance of Guild." );
continue;
}
Guild guild = ( Guild ) object;
if ( _guildMap.containsKey( guild.getName() ) ) {
System.out.println( "Duplicate guild names in guild map: " + guild.getName() );
continue;
}
_guildMap.put( guild.getName(), guild );
guild.onGuildLoaded();
} catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
continue;
}
}
}
}
}
Here is the Guild class contained within the main application.
public abstract class Guild {
public abstract String getName();
public void onGuildLoaded() {
System.out.println( "Loaded: " + getName() );
}
}
Here is the class I am trying to classload
public class Warrior extends Guild {
public String getName() {
returns "Warrior";
}
}
Here is the error it is giving me:
java.lang.NoClassDefFoundError: Guild
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$000(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.net.FactoryURLClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at GuildManager.reloadGuildFiles(GuildManager.java:53)
at Prospect.enable(Prospect.java:64)
at PluginLoader.load(PluginLoader.java:205)
at PluginLoader.reloadPlugin(PluginLoader.java:189)
at je.d(je.java:1196)
at je.a(je.java:430)
at bg.a(SourceFile:24)
at bh.a(SourceFile:218)
at je.a(je.java:56)
at dp.a(SourceFile:85)
at net.minecraft.server.MinecraftServer.h(SourceFile:267)
at net.minecraft.server.MinecraftServer.run(SourceFile:208)
at bw.run(SourceFile:482)
Caused by: java.lang.ClassNotFoundException: Guild
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.net.FactoryURLClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 25 more
From what I can gather and from what I understand, even though the main application is on the build path of the class to be loaded, the classloader does not recognize the class Guild. I think I need to try and make the classloader recognize the Guild class contained in the main application, is there anyway to do this or is there something I am clearly doing wrong?
The only possible problem I can see here is that Thread.currentThread().getContextClassLoader() for some reason produces a classloader that can't be used to access Guild class.
Try this instead:
urlcl = URLClassLoader.newInstance( new URL[] { guildDataSourceDirectory.toURI().toURL() }, Guild.class.getClassLoader());
Related
Im trying to write scripting system for a Minecraft Spigot plugin and im not quite sure on how to give my scripts the information from my main jar file.
With this current code it will compile the script fine but give me an error on line "3" of the Compass.java file (The class declaration). From what I see the program doesnt know what "com.deft.core.scripts.DeftScript" is and im not quite sure on how to tell it this information.
I got my compiling code from this thread if its relevant
Compiling the Script:
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + ";dist/Deft-Core.jar");
Iterable<? extends JavaFileObject> compilationUnit = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(script));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnit);
if (task.call()) {
Object obj = null;
try {
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./plugins/Deft-Core/").toURI().toURL()});
Class<?> loadedClass;
loadedClass = classLoader.loadClass("scripts.Compass");
obj = loadedClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (obj instanceof DeftScript) {
DeftScript deftScript = (DeftScript)obj;
deftScript.onEnable(this);
}
} else {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s%n", diagnostic.getLineNumber(), diagnostic.getSource().toUri());
}
}
Compass.java
package scripts;
public class Compass extends com.deft.core.scripts.DeftScript {
#Override
public void onEnabled() {
}
}
DeftScript.java
package com.deft.core.scripts;
public abstract class DeftScript {
public abstract void onEnable();
}
With com.deft.core.scripts.DeftScript being the source im trying to extend from the main jarfile.
Im pretty sure my problem lies from somewhere within here:
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + ";dist/Deft-Core.jar");
EDIT:
Fixed my problem with setting up the class path. My jar file in question was location in another location from what I specified.
I switch out the optionList.add calls with one
optionList.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path") + ";./plugins/Deft-Core.jar"));
All tho this fixed my initial question now it as started causing a new error.
Apon trying to run my newly compiled class file it is giving me an error
java.lang.NoClassDefFoundError: com/deft/core/scripts/DeftScript
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_45]
at java.lang.ClassLoader.defineClass(Unknown Source) ~[?:1.8.0_45]
at java.security.SecureClassLoader.defineClass(Unknown Source) ~[?:1.8.0_45]
at java.net.URLClassLoader.defineClass(Unknown Source) ~[?:1.8.0_45]
at java.net.URLClassLoader.access$100(Unknown Source) ~[?:1.8.0_45]
at java.net.URLClassLoader$1.run(Unknown Source) ~[?:1.8.0_45]
at java.net.URLClassLoader$1.run(Unknown Source) ~[?:1.8.0_45]
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_45]
at java.net.URLClassLoader.findClass(Unknown Source) ~[?:1.8.0_45]
at java.lang.ClassLoader.loadClass(Unknown Source) ~[?:1.8.0_45]
at java.lang.ClassLoader.loadClass(Unknown Source) ~[?:1.8.0_45]
at com.deft.core.main.DeftCore.onEnable(DeftCore.java:79) ~[?:?]
Im trying to create a plugin system for one of my java project so I was trying to do it with classloader but when I try my method it gives me a ClassNotFound exception. I just can't get it working could someone help me with this? Sorry for my bad english.
my method:
public void loadPlugin(String jarname) throws Exception {
File f = new File("server'\\" + jarname + ".jar");
URL url = f.toURL();
URL[] urls = new URL[]{url};
URLClassLoader child = new URLClassLoader (urls, this.getClass().getClassLoader());
Class classToLoad = Class.forName ("me.daansander.plugin.Plugin", true, child);
Method method = classToLoad.getDeclaredMethod ("onEnable");
Object instance = classToLoad.newInstance ();
Object result = method.invoke (instance);
StackTrace:
java.lang.ClassNotFoundException: me.daansander.plugin.Plugin
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at me.daansander.serverchecker.plugin.ServerPlugin.loadPlugin(ServerPlug
in.java:59)
at me.daansander.serverchecker.plugin.ServerPlugin.start(ServerPlugin.ja
va:40)
at me.daansander.serverchecker.ServerChecker.<init>(ServerChecker.java:3
9)
at me.daansander.serverchecker.ServerChecker.main(ServerChecker.java:124
)
For Class.forName() to work you'd have to register your new classloader via Thread.currentThread().setContextClassLoader(myClassLoader).
See also https://stackoverflow.com/a/4096399/1015327.
Alternatively, you could try myClassloader.findClass().
In an android app, having:
public class Pars extends Activity{
public Document docout(){
InputStream is = getResources().openRawResource(R.raw.talechap01);
Document docout = null;
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = domFactory.newDocumentBuilder();
docout = builder.parse(is);
} catch (ParserConfigurationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return docout;
}
and
public class Manager {
public static String getStory(String spage, Document docs) {
XPathExpression expr;
XPath xpath = XPathFactory.newInstance().newXPath();
Object result = null;
String num = spage;
String par = null;
try {
expr = xpath.compile("//decision"+num+"/p//text()");
result = expr.evaluate(docs, XPathConstants.NODESET);
}
catch (XPathExpressionException e) {
e.printStackTrace();
}
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
par = nodes.item(i).getNodeValue();
System.out.println(par);
}
return par;
}
}
Now I want to test the code.
public class Test {
public static void main(String[] args) {
try {Pars p = new Pars();
Document doc = p.docout();
System.out.println(Manager.getStory("000", doc));
}catch (Exception e){
e.printStackTrace();
}
}
}
I run "Test" as a java application and I get:
Exception in thread "main" java.lang.NoClassDefFoundError:
android/app/Activity at java.lang.ClassLoader.defineClass1(Native
Method) at java.lang.ClassLoader.defineClass(Unknown Source) at
java.security.SecureClassLoader.defineClass(Unknown Source) at
java.net.URLClassLoader.defineClass(Unknown Source) at
java.net.URLClassLoader.access$100(Unknown Source) at
java.net.URLClassLoader$1.run(Unknown Source) at
java.net.URLClassLoader$1.run(Unknown Source) at
java.security.AccessController.doPrivileged(Native Method) at
java.net.URLClassLoader.findClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
com.stack.over.Test.main(Test.java:11) Caused by:
java.lang.ClassNotFoundException: android.app.Activity at
java.net.URLClassLoader$1.run(Unknown Source) at
java.net.URLClassLoader$1.run(Unknown Source) at
java.security.AccessController.doPrivileged(Native Method) at
java.net.URLClassLoader.findClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) ... 13 more
I tried to find how to fix it but cannot achieve it.
I have this answer from another question but I'm kind of noob yet and can't figure it out how to fix it.
You have defined your Pars class as extending the class Activity. The
error you are seeing is telling you that the class
android.app.Activity is not available on the classpath. Maybe you need
to check which libraries you import. – adamretter Mar 5 at 10:01
Thanks all.
The error is that you're trying to run a program that references Android classes (android.app.Activity) as a regular Java program that doesn't have Android runtime libraries in its classpath.
You should run Android apps as Android apps (on an emulator or a device) and regular Java programs as regular Java programs and not mix the two. Activity is Android; main() method is regular Java.
To learn Android app development, you can start with the developer.android.com getting started documentation.
The error message says it all. You are writing Android now. Trying to write a main method doesn't make any more sense in Android than it would if you were writing a webapp that runs in Tomcat. You are going to need to step back and read up on how Android works.
I have seen many errors with Twitter4j with Android, but I am not using it for Android. I am using it for Bukkit (Minecraft Plugin). For some reason, when I add twitter4j-core-3.0.3.jar to my project, I get a error when loading in the server console:
[SEVERE] Could not load 'plugins\Test.jar' in folder 'plugins'
org.bukkit.plugin.InvalidPluginException: java.lang.NoClassDefFoundError: twitter4j/TwitterException
at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:184)
at org.bukkit.plugin.SimplePluginManager.loadPlugin(SimplePluginManager.java:305)
at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:230)
at org.bukkit.craftbukkit.v1_6_R2.CraftServer.loadPlugins(CraftServer.java:239)
at org.bukkit.craftbukkit.v1_6_R2.CraftServer.<init>(CraftServer.java:217)
at net.minecraft.server.v1_6_R2.PlayerList.<init>(PlayerList.java:56)
at net.minecraft.server.v1_6_R2.DedicatedPlayerList.<init>(SourceFile:11)
at net.minecraft.server.v1_6_R2.DedicatedServer.init(DedicatedServer.java:106)
at net.minecraft.server.v1_6_R2.MinecraftServer.run(MinecraftServer.java:391)
at net.minecraft.server.v1_6_R2.ThreadServerApplication.run(SourceFile:582)
Caused by: java.lang.NoClassDefFoundError: twitter4j/TwitterException
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:173)
... 9 more
Caused by: java.lang.ClassNotFoundException: twitter4j.TwitterException
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at org.bukkit.plugin.java.PluginClassLoader.findClass0(PluginClassLoader.java:80)
at org.bukkit.plugin.java.PluginClassLoader.findClass(PluginClassLoader.java:53)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 12 more`
Since I am new to Java, I am really not getting what I am doing wrong here. This is my code (I do not think this is the issue, since the plugin fails to load ... :
String TWITTER_CONSUMER_KEY = "XXXXXXXX";
String TWITTER_SECRET_KEY = "XXXXXX";
String TWITTER_ACCESS_TOKEN = "XXXXXXXXXX";
String TWITTER_ACCESS_TOKEN_SECRET = "XXXXXX";
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true).setOAuthConsumerKey(TWITTER_CONSUMER_KEY).setOAuthConsumerSecret(TWITTER_SECRET_KEY).setOAuthAccessToken(TWITTER_ACCESS_TOKEN).setOAuthAccessTokenSecret(TWITTER_ACCESS_TOKEN_SECRET);
TwitterFactory tf = new TwitterFactory(cb.build());
Twitter twitter = tf.getInstance();
try {
Query query = new Query(Username);
QueryResult result;
do {
result = twitter.search(query);
List<Status> tweets = result.getTweets();
for (Status tweet : tweets)
{
String rawJSON = DataObjectFactory.getRawJSON(tweet);
try
{
BufferedWriter out = new BufferedWriter(new FileWriter((getDataFolder() + File.separator + "TwitterData.txt")));
out.write(rawJSON);
out.close();
}
catch (IOException ioe)
{
ioe.printStackTrace();
System.out.println("Failed to store tweets: " + ioe.getMessage());
}
}
}
while ((query = result.nextQuery()) != null);
System.exit(0);
}
catch (TwitterException te)
{
te.printStackTrace();
System.out.println("Failed to search tweets: " + te.getMessage());
System.exit(-1);
}
Why is Twitter4j not loading properly? Thanks!
EDIT: Also, the twitter4j-core-3.03.jar is in another directory on my computer. Is that a issue?
All jars needed by your Java program need to be on the classpath on the server. This includes Twitter4J in your case.
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 :
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.
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 :
Disable SecurityManager to be abled to play with my custom
classloader
Code my own classloader derived from URLClassLoader that fix URL when they are returned
Set its classpath with the claspath extracted from the
JNLPClassLoader
Set this custom classloader to be the context classloader
Set this custom classloader to be the AWT-Event-Thread context
classloader
Use this custom classloader to load my application entry point.
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...