Can a loaded JAR be deleted by the Java-Process? - java

Hello I have following Problem:
Within an uninstall-process I load a JAR (jdbc-driver).
URL pDriverJar = jarToDelete.toURI().toURL();
URL[] lURLList = new URL[]{pDriverJar};
URLClassLoader lLoader = new URLClassLoader(lURLList, Thread.currentThread().getContextClassLoader());
Thread.currentThread().setContextClassLoader(lLoader);
Class<?> aClass = Class.forName("jdbc.Driver"); // was Oracle: oracle.jdbc.OracleDriver but should not be important
if(jarToDelete.delete()){
System.out.println("deleted");
}else {
jarToDelete.deleteOnExit();
}
After terminiation of the JVM, the jar is still existant.
As a workarround, I've created a tempfile, and copied the Jar to that tempfile. But now the Tempfile will not be deleted.
I've read, that if the ClassLoad is GC, the loaded jars can be removed.
Does anyone have an Idea, how to delete this File?

It depends on the operating system. Windows will not let you delete files that are in-use, but Linux will.
One solution would be to start a second process that waits for your JVM to die and then deletes the file, as even if you clear all references to classloaders using it, there is no guarantee that they will release the file. There is no way to force garbage collection (or even finalization) of an object.
Another solution would be to write the classloader that loads the Jar. That way, when you want to get rid of it, you can be certain that the Jar is closed. If the only object that opened it was your classloader, then you can be certain it is free and should be deletable.

This issue was fixed in Java 7; use the close() method in the ClassLoader class. For older versions, there are several options:
Write a custom classloader, e.g. like this
Use reflection and close all JarFile instances; they reside in sun.misc.URLClassPath

As I wanted to kill off a jar-file on the classpath that I needed for java11 but led to problems in java 1.8 or below; A solution that I used, without having to start another process to kill the jar file, was to just make the file 0 bytes by
FileOutputStream out = new FileOutputStream(f);
out.write(new byte[0]);
out.flush(); out.close();
And next time the application starts this code then manages to delete it, even though the 0-byte-file is still in the manifest:
if (f.exists()){
if (f.delete() == false) f.deleteOnExit();
}

Related

How to extract java preview class files from jdk 15?

I am currently trying to extract from the jdk the preview class files such as java.lang.Record from jrt-fs.jar (in libs folder), but it does not find the preview classes such as Record when iterating over it. This is the code I am using:
Path jrtFsJar = jdk15Home.resolve("lib").resolve("jrt-fs.jar");
jrtFsJarLoader = new URLClassLoader(new URL[] {jrtFsJar.toUri().toURL()});
FileSystem jrtFs = FileSystems.newFileSystem(
URI.create("jrt:/"),
Collections.emptyMap(),
jrtFsJarLoader);
Files.walk(jrtFs.getPath("/modules")).forEach(path ->
// Here is walks over classes such as "modules/java.base/java/lang/Object.class"
// but not over "modules/java.base/java/lang/Record.class"
)
I have also tried a more direct approach:
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
// Object works, I get the bytes.
byte[] object = Files.readAllBytes(jrtFs.getPath("modules", "java.base",
"java/lang/Object.class"));
// Record fails, NoSuchFile.
byte[] object = Files.readAllBytes(jrtFs.getPath("modules", "java.base",
"java/lang/Record.class"));
Now when I'm running the exact same jdk with --enable-preview, I can use records.
How do I extract the Record class from the jdk linux/lib? Are the preview class inside of it or should I look for them elsewhere? Do I need a specific flag to access them?
Any help is appreciated.
When you are running on a JDK that has a jrt file system already, you can access the jrt file system of another JDK much easier.
FileSystem jrtFs = FileSystems.newFileSystem(
URI.create("jrt:/"), Map.of("java.home", jdk15Home.toString()));
byte[] object = Files.readAllBytes(
jrtFs.getPath("modules", "java.base", "java/lang/Record.class"));
The built-in file system will create a special class loader for the foreign JDK’s jrt-fs.jar that does not delegate to the parent loader for the classes of this jar. So it does not end up at its own imple­men­tation again.
Since it uses the implementation provided by the other JDK, it will be able to handle newer features or even JDKs using an entirely different data format for its module storage.

How to change a class inside jasper jar file

I'm facing some issues with jasper and I need to try to edit SmapUtil class inside jasper.jar file
However, I'm facing some problems to do so.
I've used jd-gui to decompile the jasper.jar file, took out the SmapUtil.java file, changed the
install method from
static void install(File classFile, byte[] smap) throws IOException {
File tmpFile = new File(classFile.getPath() + "tmp");
SDEInstaller installer = new SDEInstaller(classFile, smap);
installer.install(tmpFile);
if (!classFile.delete()) {
throw new IOException("classFile.delete() failed");
}
if (!tmpFile.renameTo(classFile)) {
throw new IOException("tmpFile.renameTo(classFile) failed");
}
}
to
static void install(File classFile, byte[] smap){
File tmpFile = new File(classFile.getPath() + "tmp");
SDEInstaller installer = new SDEInstaller(classFile, smap);
installer.install(tmpFile);
while (!classFile.delete());
while (!tmpFile.renameTo(classFile));
}
it's basically to keep trying to delete the file if it doens't work the first time.
Now it's where I'm facing my problem.
If I try to compile SmapUtil.java, I face a lot of missing sources.
I've tried using javac -classpath (original)jasper.jar SmapUtil.java, but I still have a lot of sources missings.
I've downloaded a jasper-sources.jar file from god knows where and used that as a -classpath, but the missing sources remains..
How should I do that? I don't think that it should that hard to change 2 lines of a file inside a jar..
Thankss
Compiling a large project such as Tomcat can be a fairly complicated endeavour. If you try decompiling/editing/recompiling without knowing this process you will probably run into a lot of issues.
It might easier (or at least predictable) to build the entire project from sources. Once you have managed to build it, you can try editing the sources.
You should be able to checkout the sources from the project's website.
If you need to patch this class because you feel it's not working correctly, it might be worth trying submitting a bugreport

java.util.MissingResourceException - new File() does not reach into jar - how to provide external library with a path for this File?

The question started as - maven does not reach into jar for a folder bundle correctly, when running tests. It does work currectly when running some main() though.
The stack trace for running test (while building) looked like this:
Caused by: java.util.MissingResourceException: Can't find Not found profile: file:\C:\Users\Simon\.m2\repository\ario\TextProcessing\1.0.4-SNAPSHOT\TextProcessing-1.0.4-SNAPSHOT.jar!\lang bundle
at java.util.logging.Logger.setupResourceInfo(Logger.java:1942)
at java.util.logging.Logger.<init>(Logger.java:380)
at java.util.logging.LogManager.demandLogger(LogManager.java:554)
at java.util.logging.Logger.demandLogger(Logger.java:455)
at java.util.logging.Logger.getLogger(Logger.java:553)
at cz.techniserv.ario.tagger.TagDetect.setLangPath(TagDetect.java:126)
at cz.techniserv.ario.tagger.TagDetect.<init>(TagDetect.java:58)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 86 more
I triple checked - the lang folder is within the jar.
When running a main() within the module however, it does not try to reach into jar withing m2 repository, but will take the lang folder from within the opened dependency project. So it does not try to reach into a jar, just takes the data from project folder. This runs correctly.
Apparently, the File() constructor is unable to reach for the resource in the jar.
The code, that tries to reach into the jar is within a constructor. It looks like this:
this.path = this.getClass().getResource("/" + aLanguageDirectoryName).getPath();
and than there is a File constructor which takes this path.
In case of running the app it resolves to the project path. When it runs tests it will resolve to the jar within the m2 repository and tries to reach within the path that is in the exception:
file:\C:\Users\Simon\.m2\repository\ario\TextProcessing\1.0.4-SNAPSHOT\TextProcessing-1.0.4-SNAPSHOT.jar!\lang
What would you do? Would you copy the resource "lang" folder on some temporary path and provide the library with this new non-within-jar temporary path, so that it can open with the File() constructor? Or do you see another better way?
Ok, so what we did is that the data are ad hoc extracted to classpath and accessed.
The problem with this solution is that if you delete the files after usage, than you will always extract and delete data with each run - when this happens with every spring initialization of running tests this can happen extreme amount of times and take a lot of time. Leaving the data there however means that if the path is not absolute and out of the project trunk folder, SCM will pick it up unless you ignore it and commit the data which is not satisfying. Also they get packed into the jar upon build since they are on classpath which is another drawback. Yes, you can ignore with SCM and configure maven to exclude the folder, however some developers will forget (one already did) to ignore with SCM and it was commited.
We consider extracting to an absolute path which would not be on classpath a bad practice - firstly because you have no control on what system the projects run on so you cannot guess very well the absolute path - also it does not look pretty to throw data around your computer for bad design.
So I guess the best thing would be to push everyone to place the data on some place on their discs and set an environment variable which would be the same for everyone. This makes the project less portable, requires more configuration, but removes formerly mentioned problems.
I did not come up with anything better.
In case anyone would want to do the same thing, here is our code:
CodeSource src = this.getClass().getProtectionDomain().getCodeSource();
if (src == null) {
return null;
}
URL jarURL = src.getLocation();
try (JarFile jar = new JarFile(jarURL.getPath());) {
Enumeration<JarEntry> enumEntries = jar.entries();
while (enumEntries.hasMoreElements()) {
JarEntry fileFromJar = (JarEntry) enumEntries.nextElement();
File toBeCreatedFileLocally = new File(NAME_FOR_TEMPORARY_FOLDER_TO_HOLD_LANG_DATA_FROM_JAR + File.separator + fileFromJar.getName());
if (fileFromJar.isDirectory()) {
continue;
}
if (fileFromJar.getName().contains(aLanguageDirectoryName)) {
toBeCreatedFileLocally.getParentFile().mkdirs();
try (InputStream is = jar.getInputStream(fileFromJar); // get the input stream
FileOutputStream fos = new FileOutputStream(toBeCreatedFileLocally)) {
while (is.available() > 0) { // write contents of 'is' to 'fos'
fos.write(is.read());
}
}
}
}
} catch (IOException ex) {
Logger.getLogger(TagDetect.class.getName()).log(Level.SEVERE, null, ex);
}
This code is actually a modification of a different answer here https://stackoverflow.com/a/1529707/1920149 (beware, that answer has a bug, check comments - they rejected my edit)

Extract resource folder from running jar in Java 7

My resources folder inside my jar includes a directory with several binary files. I am attempting to use this code to extract them:
try(InputStream is = ExternalHTMLThumbnail.class.getResourceAsStream("/wkhtmltoimage")) {
Files.copy(is, Paths.get("/home/dan/wkhtmltoimage");
}
This is throwing the error
java.nio.file.NoSuchFileException: /home/dan/wkhtmltoimage
Which comes from
if (errno() == UnixConstants.ENOENT)
return new NoSuchFileException(file, other, null);
in UnixException.java. Even though in Files.java the correct options are passed:
ostream = newOutputStream(target, StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE);
from Files.copy. Of course there's not! That's why I'm trying to make it. I don't yet understand Path and Files enough to do this right. What's the best way to extract the directory and all its contents?
Confused because the docs for Files.copy claims
By default, the copy fails if the target file already exists or is a symbolic link
(Apparently it fails if the target file doesn't exist as well?)
And lists the possible exceptions, and NoSuchFileException is not one of them.
If you're using Guava:
URL url = Resources.getResource(ExternalHTMLThumbnail.class, "wkhtmltoimage");
byte[] bytes = Resources.toByteArray(url);
Files.write(bytes, new File("/my/path/myFile"));
You could of course just chain that all into one line; I declared the variables to make it more readable.
The file that does not exist may actually be the directory you're trying to create the file in.
/home/dan/wkhtmltoimage
Does /home/dan exist? Probably not if you're on a Mac.

Java - how to find out if a directory is being used by another process?

I want to use java 7's WatchService to monitor changes to a directory.
It seems it tries to lock the folder, and will throw an exception if it fails, but does not seem to provide any method of locking it before-hand / checking if it is already locked.
I need to know if a directory is currently being used by another a process or not.
Since I can't lock it or open a stream to it (because it's a directory), I'm looking for something more intelligent than trying to modify it and sleeping if failed, or try/catch with sleep.
Ideally, I would like a blocking call until it is available.
EDIT:
I can't seem to acquire a FileLock on the folder.
When I try to lock the folder, I get "FileNotFoundException (access denied)".
Googling suggests you can't use that object on a directory.
registration code:
WatchService watchService = path.getFileSystem().newWatchService()
path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE)
Failing scenario:
Let's say I'm listening to a folder f for new creation.
If a sub-folder g is created in it, I want to listen to changes in g.
However, if I create a new folder in f (in Windows), this will fail because Windows is locking the folder until a name is given.
Thanks
Taken from here
File file = new File(fileName);
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
// Get an exclusive lock on the whole file
FileLock lock = channel.lock();
try {
lock = channel.tryLock();
// Ok. You get the lock
} catch (OverlappingFileLockException e) {
// File is open by someone else
} finally {
lock.release();
}
After all the comments, and since your problem looks particular to windows, I wanted to suggest the following library:
http://jpathwatch.wordpress.com/
if you read in the features, you can see the following:
Changes in subdirectories* (recursive monitoring)
this is what you need. seems it does it for you without you having to register every new directory by hand. it is limited to selected platforms. and when checking that, it seems that is available only in windows !!!! see here: http://jpathwatch.wordpress.com/documentation/features/
a very important thing is the possibility to invalidate when a watched directory becomes unavailable. (using java watch service, it a directory is monitored and gets renamed, you still get events with the old path !!)
I think this library would be the most elegant and will save a lot of coding for you for this case.

Categories