Reload existing class file dynamically for Java Application - java

Scenario
I am working on a big application consist of hundred of class files. As part of giving a patch to fix the issue in production set up, we generally give a jar file. This jar file contains the class file(s) which includes a fix and put inside one patch path(This is the path which is used to put the patches/fixes as jar file). In the production set up, we recommend to restart the application. As part of restart , application first looks the class files from patch path and then from the other path where all the class files are present. So this way first classes from patch path is loaded.
Expectation
Now instead of restarting the application, i want to reload the class file so that restart of application can be avoided. I checked in net and found the following:
We can use customize/dynamic class loader as well. I think this is also not full proof way of doing it.
URLClassLoader is also one of the option.
If we use customize class loader, then we have to load all the application class files using this because class file can refer to another class file if it is loaded from the same class loader. This is what i read from net. If yes, then i think this option is not valid. right ?
After checking all the above options, i still feel the above options are not good enough to load the existing class file (which is getting used in running application as well) at run time.
So question arises that is there any full proof possibily to replace the class file at run time for java application.
if Yes, Could you please guide in this regard.
Also, if this kind of requirement or question is already answered properly(I didn't find any good link), please redirect
NOTE: I think OSGi is also one option. But i feel not possible in such a big application since it will requires lot of changes which i want to avoid.
Please suggest.

Related

Where should I place a file when the path is "./"?

I am maintaining a Spring Boot project. There is this code:
BufferedReader reader = new BufferedReader(new FileReader("./setting_mail_sender.txt"));
Where should the file be located in this case?
In 'the current working directory'. And where is that? Who knows!
Whomever wrote that code messed up. It's not a good idea to use the CWD for anything in any java code unless you specifically know you want it. And generally, that's only the case when you're writing command line tools in the vein of the various tools you find in your average linux distro's /bin dir - a rare occurrence, given that the JVM isn't really designed for that kind of thing.
There are 3 different best practices for 'data files' depending on the nature of the data:
Static, unchanging data - as much part of your app as your class files are. These should be loaded with MyClass.class.getResource("name-of-resource.txt") and shipped the same way your classes are. For example, inside the jar file.
Config files. These should usually be in System.getProperty("user.home") - the user's home dir; /Users/yourusername on macs, /home/yourusername on linux, C:\Users\YourUserName on windows. Best practice is to ship a 'template' version of the settings file if relevant in the jar file, and upon detecting that there is no config file present at all, to write out the template (and you load the template in via MyClass.class.getResource). If a template is not a good idea, something in a similar vein. Bad practice is to have the installer do this, and have your app be broken or leave the user having to peruse complex documentation to (re)create the config file. A different way to do it right is to have a config page in your app (a window, menu bar setting, web app thing - something with a user interface) where you can change settings and the config file is simply the way you store that data.
Changing data files. For example, you ship H2 (an all-java database engine) with your app and it needs to write its database file somewhere. This is a bit tricky; user home is not the right place for such data files, but you can't really 'find' the dir where your app is installed either. Even if you can, on non-badly-designed OSes, apps usually cannot (and should not!) be able to write to that location anyway. The location where this data is stored should definitely be configurable, so one easy way out is to require that the user explicitly picks a place. Otherwise I'm afraid you're stuck having to write per-OS code - find /Users/myusername/Library/Application Support/yourappname on mac, which is the right place. As far as I know there is no library to do this right.
None of them involve 'require that the user start the app with the right CWD'. There are good reasons for that: It can be hard to configure, and it's not something users think of to configure. For example, when setting up a java app as a recurring task in windows, you can configure the working dir for such a process, but it's not something that's usually considered as crucial configuration. When running a java app from the command line, who knows what the working dir is. You'll end up with an app that usually works, except in some circumstances when it magically doesn't, and most of your users have no idea that the difference between the magic run that works and the one that does not, is the directory they were in when they started the java app.
If you can edit that code, do so - figure out which of the 3 different kinds of data this is about (sounds like the second bullet: Config stuff, so should be in user home, and the app's name should be part of the file name) - and fix it. for example, that should be:
try (var in = Files.newBufferedReader(Paths.get(System.getProperty("user.home"), "myapp-mail.conf")) {
}
This solves a whole bunch of problems:
Uses try-with to avoid resource leakage.
Reads from user.home, avoiding current working directory as relevant setting.
Actually uses UTF-8 encoding (whereas your code will, at least until java 17, do 'platform default'. It's somewhat unlikely you want that, as it means your config file is not portable; copying it from one computer to another may break things. You presumably don't want this.
If errors occur, the error messages are improved (one of the downsides of the 'old' file API).
If you can't change this code, figure out what the CWD is; put the files there, and ensure that, however you start this spring boot project, you always start it from that directory. If you can't change this code but you can run some code in that JVM, you can print it: System.out.println(Paths.get(".").toAbsolutePath()) will show it to you.

Read a file from same folder as JAR file but still read resources folder when loading from IDE

I've been trying to make jar application that can read a csv file in the same directory as it. This is, however, proving difficult as my means for accessing the file currently is:
InputStream is = getClass().getClassLoader().getResourceAsStream(filename);
Which works for my program running in the IDE and for my tests but doesn't work when I run the program from the compiled jar file. I have no idea how to get it to work for both. I seriously can't understand this path stuff, it seems like there are a million ways to do it and only one of them work for only one specific scenario.
I've been trying to make jar application that can read a csv file in the same directory as it.
Ah, there's your problem. That just isn't a thing.
There are only 2 types of files:
Application Resources
These are read only, and are as much part of your app as your class files are. It is not in any way relevant to think about 'editing' them - that's not the kind of thing they are. It is reasonable to assume that if this resource is somehow missing, the app is as corrupt / misinstalled as it would be if class files are missing.
For this, you use .getResource and .getResourceAsStream. And note that getClass().getClassLoader() is wrong, you want MyClass.class.getResource and then add a slash if you want to go from root (because getClass() potentially breaks when you subclass, and going via classloader is [A] just typing for no reason, and [B] breaks in bootload scenarios. MyOwnClassName.class.getResource never breaks, so, always use that).
This asks java to look in the same place class files are and nowhere else. Your class files are inside the jar files, and not next to them, therefore, it won't find a text file that is sitting next to jar files.
it does not make sense that it does work during development: That means you shoved a file inside the resources folder, which is equivalent to having a CSV file inside the jar file. You must have gone out of your way to tell your build system to do weird things. Don't do that.
If that CSV file is not intended to be user editable it should be inside the jar file and not next to it: That makes it an application resource. Examples of application resources:
You have a GUI, and you need to store the icon files and splash screen art and such someplace.
You ship static data with your app, such as a table of all US states along with the zipcodes they use (could be a text or csv file for example).
Templates of config files. Not config files themselves.
DLLs and the like that you need to unpack (because windows/linux/mac isn't going to look inside jars for them).
You're a webapp and you want to ship the HTML static files along with your webapp.
If this is what your CSV file is, the fix is to put it in the jar, not next to it, then load it with MyClass.class.getResource(name).
Config files and project files
For example:
For a rich text editor (like, say, LibreOffice Writer), the .odt files representing your writings.
Save games for a game.
A config file, which can be edited by the user, or is edited by your own app in a 'preferences' dialog. This stores for example whether to open the app full screen or not, or authentication info for a third party API you're using.
These should not be in the jar, should not be loaded with .getResource at all, and should not be in src/main/resources in the first place.
They also should not be next to your jar! That's an outdated and insecure model (the idea that editable files sit in the same place the app itself sits): A proper OS configuration means that an app cannot write to itself which is most easily accomplished by having it be incapable of writing to its directory. Some OSes (notably, windows) did this wrong for a while.
For example on windows, your app lives in C:\Program Files\MakorisAwesomeApp\makori.jar, and the data files for it live somewhere in C:\Users\UserThatInstalledIt\Documents\MakorisAwesomeApp.
oh linux, your app might be /usr/bin/makori and the data lives somewhere in the home dir. Config data might live in /etc/.
You don't "ship" your config files, you instead make installers that create them. You can do this part in-app by detecting that the relevant config file does not exist, load in a template (that is a resource, shipped inside your jar, loaded with getResource), and write it out, and tell the user to go look at it and edit it.
I really want a CSV file next to my jars!
Well, that's wrong, so, there are no libraries that make this easy. When you want to do silly things its good that APIs don't make that easy, right?
There are really hacky ways to do this. You can use .getResource to get a URL and then 'parse' this. This breaks the classloader abstraction concept (because in java, you can write your own classloaders and they can load from anywhere, not just files or entries in jars), but you can ask for 'yourself' (MyClass.class.getResource("MyClass.class")), pull the URL apart and figure out what's happening - does it start with file://? Then it is a file, so turn it into a j.i.File object, and go from there. Does it start with jar://? find the !, substring out the jar part, and now you know the jar. Make that a java.io.File, ask for the parent dir, and look there for the CSV.
You have to write all this. It's complicated code that is hard to test. You should not do this.

Refer to class outside jar/war file

Is it possible that we keep our jar/war file running and one class file in the same directory which can be referred from jar/war? The requirement is I want to keep some common code in single file which can be referred by multiple running jars/wars and I don't have to re-deploy application if I can successfully just change the class file.
I would go for this question here: How should I load Jars dynamically at runtime?
And them try the answers from jodonnell, chris and allain-lalonde depending on how do you want to access these class files, if by URL or by file path, and if you want to use JAR files or CLASS files.
In any case you should try and see it for yourself if it fits your needs and is acceptable by your employer standards. At certain point, we need to think out of the box. Don't worry if one or other user dislikes this or that approach. This community if meant for ask and receive answers on how to do it, and not to not do it.

How can I make a loaded jar use resources from itself rather than its loader?

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!
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.

How make working directory files available to WebStart application?

We have to make a Java application demo available on Internet using JWS. It goes pretty well; all that's missing is making working directory files available for the application.
We know about the getResource() way... The problem is that we have different plugins for the same application and each one require different files (and often different versions of the same files) in their working directory to work properly. So we just change the working directory when we want the application to have a different behavior.
Currently, the demo is packaged in a signed jar file and it loads correctly until it requires a file from the working directory. Obviously, the Internet users of this demo don't have the files ready. We need a way to make these files available to the WebStart application (read/write access) in its working directory.
We've thought of some things, like having the application download the files itself when it starts, or package them in the jar and extract them on startup.
Looking for advices and/or new ideas. I'll continue working on this... I'll update if I ever find something reliable.
Thank you very much!
I said I would share what I found in my research for something that would fit my needs. Here's what I have so far.
I have found that the concept of current working directory (CWD) does not really make sense in the context of a Java Web Start (JWS) application. This had for effect that I stopped trying to find the CWD of a JWS and started looking for other options.
I have found that (no, I didn't know that) you can refer (using getResource()) to a file in the root directory of a JAR file by simply adding a '/' in front of its name. ("/log4j.properties", for example.) The impact of this is that I can now take any file which is only referred to in a read-only manner in the root of that JAR file (which is really only a ZIP file). You can refer to any file in the root of the JAR file using AnyClass.class.getResourceAsStream. That rules out the problem with read-only files required to run the application, at the cost of a switch in the code telling whether the application is run from a valid CWD or from a JWS context. (You can very simply set a property in the JNLP file of the JWS application and check if that property is set or not to know where to look for the file.)
For write-only files (log files in my case), I used the property , adding a directory with the name of the application: <user.home>/.appname and added log files to it.
Read/write files (which I don't have in my case) would probably simply go at the same place than write-only files. The software could deal with uploading them somewhere if needed, once modified, I guess.
That's the way I deal with the problem for now.
Note there is a service you can explicitly ask for, to get file access to the computer (unless you go all the way and ask for full access (which requires signed jar files)).
Then you need to determine where these files need to go - basically you have no idea what is where and whether you may actually write anywhere. You can create tmp-files but those go away.
Would a file system abstraction talking to the JNLP-server do so you store the users data on the server?

Categories

Resources