0

Summary: When I load and run one jar from another jar and that loaded jar attempts to access its resources inside itself, it is unable to find those resources. I believe this is because it is looking inside the jar that loaded it instead of itself. How can I fix this?

The problem in more detail:

I am attempting to programmatically load a jar file -- let's call it "Server" -- into the Java Virtual Machine through my own Java program -- let's call it "ServerAPI" -- and use extension and some other tricks to modify the behavior of and interact with Server. ServerAPI depends on Server, but if Server is not present, ServerAPI still has to be able to run and download Server from a website.

To avoid errors caused by ServerAPI loading without satisfying its dependencies from Server, I have made a launcher -- let's call it "Launcher" -- that is intended to download Server and set up ServerAPI as necessary, then load Server and ServerAPI, then run ServerAPI.

I have gotten to the point where Launcher is successfully able to load Server and ServerAPI. However, when I attempt to run Server through ServerAPI, errors appear because Server is unable to locate certain resources. There resources are located inside the Server jar as they always are.

I had the idea that perhaps, since the resource paths all seem to be relative, Java is searching for these resources inside the jar that initially loaded and ran the entire program, i.e. Launcher. To test this, I took some of those missing resources from the Server jar and put them in the same path in the Launcher jar. The errors related to those missing resources disappeared.

So, then, I need a way to ensure that all resources loaded by code in the Server are found in that jar instead of making Java search for them in the Launcher jar. Copying the assets into the Launcher jar worked for my test, but it would be inconvenient and error-prone for the final product, not to mention the fact that it would increase file sizes as there are a large number of resources to copy and that making multiple copies of the same files could lead to errors under certain circumstances.

Ideas that I have already thought of that will not work:

I cannot copy the resources into the Launcher jar because it would increase the size of Launcher; it further complicates the program and can potentially cause errors in cases where sometimes Server is run on its own and sometimes it is run through ServerAPI using Launcher; and it would not allow ServerAPI to be adapted for use with more than one version of Server.

I cannot move all the resources out of Server and into Launcher to eliminate redundancies and ambiguity because for complicated legal reasons, I cannot modify Server. I can, however, modify Launcher or ServerAPI freely, for I created them.

I cannot modify the paths of the resource calls. My tricks for modifying the behavior of Server using ServerAPI will not work in these sections of the code and even if they did, there are so many calls to so many resources that it would take ages to find them all and modify them all.

Research I have already done on this problem:

This question is the only question I've found that seems to be related to the issue, but I cannot modify each call to the resources for the reason mentioned in the paragraph above.

//EDIT:

It seems that Server's resource calls primarily use the format CLASS.class.getResourceAsStream("/RESOURCE"). Does this help at all?

//END EDIT

//EDIT 2:

I tried using Thread.currentThread().setContextClassLoader() to set the ClassLoader to the one I used to load Server, hoping that since I was using the ClassLoader used to load Server that all resource paths would now be relative to Server. Unfortunately, this did not work.

//END EDIT

//EDIT 3:

Up until now, I was initializing the parent of the ClassLoader used to load Server to be Launcher's own ClassLoader, so I just tried setting the parent to null instead to make it act as a standalone ClassLoader, hoping that would break its bond to Launcher. That didn't work either.

//END EDIT

Any help would be greatly appreciated! Thanks in advance!

Community
  • 1
  • 1
Variadicism
  • 624
  • 1
  • 7
  • 17
  • could you add another jar in classpath of META_INF/manifest.mf in first jar and check ? – Naveen Ramawat Oct 14 '14 at 04:21
  • @NaveenRamawat Unfortunately, because the Launcher needs to be able to run without relying on Server and ServerAPI, I can't add them to the classpath. I'm loading them programmatically using `URLClassLoader`s similar to the way in the answer to [this question](http://stackoverflow.com/questions/11016092/how-to-load-classes-at-runtime-from-a-folder-or-jar). – Variadicism Oct 14 '14 at 04:27

1 Answers1

1

Through some trickery and hackish fixes, I managed to make it work!

What I did is instead of using the standard URLClassLoader to load the classes in Server and ServerAPI, I used my own custom ClassLoader class that extends URLClassLoader. It worked basically the same way except that I overrode the getResource() method to prepend (attach to the beginning) the absolute path URL of the jar file, basically forcing it to look for the resource in its specific jar file instead of making it relative to the ClassLoader.

The code for that overridden method looks like this:

@Override
public URL getResource(String path) {
    /* eliminate the "/" at the beginning if there is one to avoid 
    conflicts with the "!/" at the end of the URL */
    if (path.startsWith("/"))
        path.substring(1);

    /* prepend "jar:" to indicate that the URL points to a jar file and use 
    "!/" as the separator to indicate that the resource being loaded is 
    inside the jar file */
    String URL_string = "jar:" + getURLs()[0] + "!/" + path;
    try {
        return new URL(URL_string);
    } catch (MalformedURLException exception) {
        System.out.println("There was something wrong with the URL representing this resource!");
        System.out.println("URL=\"" + URL_string + "\"");
        exception.printStackTrace();
        return null;
    }
}

I had to learn this the hard way and come up with the fix myself; I hope this answer helps someone else with the same problem!

Note: This also fixes the Class.getResource() method because Class.getResource() simply prepends a "/" if there isn't already one there, then calls ClassLoader.getResource(). It seems to fix Class(Loader).getResourceAsStream() as well, but don't quote me on that one.

Variadicism
  • 624
  • 1
  • 7
  • 17