I need to access some resources (for a contextual help system) which may be located either in a directory tree (somewhere in the filesystem) or contained into a specific .jar.
Resources in the filesystem should take precedence over "packed" ones.
I should provide a runtime (not a compile-time action) command to migrate all resources from filesytem directory tree to the .jar package.
I want to build a separate .jar to contain just those resources. I don't want/need to modify code. I need to deliver a product with minimal help and allow the costomer to refine it and update need to recompile. Customer will then distribute the "improved" version.
What is the best way to implement this?
I'm interested specifically in the "migration" part, since I should be able to code the resource access part, but comments are always welcome.
There are various ways this can be implemented but re-using Java's ability to read resources from a directory on the classpath looks the easiest to me.
Deliver the application with a zip-file containing the updatable help-resources. At application start, extract this zip-file to apphome/help-resources but do not overwrite newer/modified files. Use the technique in this answer to add the apphome/help-resources directory to the class-path. From then on resources can be loaded as if they are in a jar-file on the classpath (no special code needed).
The customer can update the files in apphome/help-resources and using a runtime-command the zip-file can be updated/recreated with the files in the apphome/help-resources directory. Or if the customer has 7-zip or something similar, just create the zip-file using the zip-tool.
Related
in my Java project I am using an H2 in-memory database, for which I have to load the JDBC driver when I initialize my application. I want/need to load the H2 .jar file dynamically, so I do the following:
String classname = "org.h2.Driver";
URL u = new URL("jar:file:libs/h2.jar!/");
URLClassLoader ucl = new URLClassLoader(new URL[] { u });
Driver d = (Driver) Class.forName(classname, true, ucl).newInstance();
DriverManager.registerDriver(new DriverShim(d));
When I put the H2 .jar file into a "libs" folder outside my Java source code folder (that is, in Eclipse, this "libs" directory is on the same level as the "src" folder), then this approach works fine. However, unfortunately I have to put this H2 .jar file into a folder within the source code folder tree, but below the main class folder.
For example, my Java package structure looks like this in Eclipse:
<project>/src/my/app/MyApp.java // main class of my application
<project>/src/my/app/sub/package/h2.jar // how to access this?
<project>/libs/h2.jar // loading from here works
I know this is stupid, but unfortunately I have to work with this strange setup. But what I don't know: how can I edit my Java code (listed above) in order to work with this setup?
EDIT: This has to work outside Eclipse as well, so adding the JAR file to the Java Build Path in Eclipse is no option for me.
EDIT2: I already tried to load "jar:file:my/app/sub/package/h2.jar!/", but that did not work for me.
Thanks in advance for all helpful ideas!
Kind regards, Matthias
In some frameworks referring to files inside JARs can be done using the classpath: prefix. I doubt URLClassLoader supports it natively, but it's worth a try (e.g. classpath:/my/app/sub/package/h2.jar). But since that doesn't work with URLClassLoader, here are other ways:
One way to do it would be to write your own ClassLoader which reads the JAR file from classpath (using getResourceAsStream), uncompresses it (using ZipInputStream) to memory (e.g. a map of byte arrays) and loads the classes from there.
Another, slightly easier way, is to read the JAR file from classpath and write it into a temporary file. Then you can use the plain URLClassLoader to load classes from it. This has the disadvantage that the file must be written to a file and the file probably cannot be removed until the JVM exits (unless using Java 7 or higher).
I'm using the second approach (copying to a temp file) in one project, though I'm using it to launch an external process. I would be curious to hear why you have such a requirement. If it's just a matter of having the whole application in one JAR, there are numerous simpler methods for achieving that (Maven Assembly Plugin, Maven Shade Plugin, Jar Jar Links, One-JAR to name a few).
No it's not a homework, but an online build system that uses my classes under my/app/* and several other classes (not from me) to automatically build the whole solution. Anyway, I can't give you more details on the internals of this system, as I don't know them. As said, I simply have to live with it, and that is why I am asking here...
Sounds like you are working in a WTF environment (does it have a name?), so here are some ways to start hacking around it:
Find out more about your environment, especially absolute file paths of the following: directory where the source files are saved, directory where the generated .class files are saved, and the current working directory when the program is run.
If you can get any kind of output of what your program prints during runtime, you can put into your application some debug code where you use File.listFiles() to crawl the machine's directory trees. If you can get output only from what happens when compiling, it might be possible to execute your own code during compile by creating your own annotation processor (apt is part of javac since Java 6), though I'm not sure whether the annotation processor must be compiled first separately.
The working directory can be read from the user.dir system property and the location of class files can be probably gotten from the java.class.path system property (unless custom class loaders are used). There is no guarantee that a JAR file in the source directory would be copied to the classpath, so you might need to do some looking around.
Then when you know the file path of the JAR file, then you can get an URL to it using new File("path/to/h2.jar").toURI().toURL() which you can then pass to URLClassLoader.
If nothing else works, upload the source code of the libraries and compile them together with your project.
In the long run, try to replace the WTF build environment with one that uses a standard build tool (such as Maven) and a common CI server (such as Jenkins). It's normal for projects to have lots of library dependencies, so you shouldn't need to hack around a build environment to use them.
How can I splice files (ANY files, pngs, classes you name it) and folders into a pre-existing jar file ONLY using java code. That means no jar.exe utility. I am planning to make a program that places files/folders into a specific jar file. I have looked at several tutorials on java.util.jar and other jar managing modules but none of them seem to be right for me. Do I have to do this from scratch? Here is an over view of what I am thinking:
Take the desired files and folders from within a specific folder
open the target jar and place the files and folders within, overwriting other files if need be
I'm thinking I have to decompile the jar and then repackage it (ONLY USING JAVA CODE). I don't know what to do.
In one of my JUnit tests, I am trying to load all the files contained in a directory. I used .getClassLoader().getResource("otherresources") to find the directory. I then made a new java.io.File. I then used listFiles() to get all the children files and then used .getClassLoader().getResource() again to load each of those files.
URL url = FileLoadTest.class.getClassLoader().getResource("otherresources");
File directory = new File(url.getPath());
File[] files = directory.listFiles();
Basically, I want to be able to load all the files in a directory without knowing exactly what they are.
I can properly run the test in Eclipse. When I go to build the project with Maven (mvn install) or run the test case by itself using surefire (mvn -Dtest=FileTest test) the test case fails with a NullPointerException. I think the issue has something to do with the File api not working as intended within the JAR file that the resources are deployed to.
Any tips on how to fix this?
Correct, the File API can only read from the file system, and not within JAR files.
Unfortunately, there's not a good way to do exactly what you're trying to do, without using some additional libraries that utilize some hacks in an attempt to accomplish just this. Resources on the classpath are not meant to be enumerable, as there is no guarantee where they are loaded from (could be from disk, in a JAR file, HTTP, a database, or other exotic resources - including ones where the enumeration of available files would not be feasible). The best approach is to include an "index" or other similar file with a well-known name, which can be referenced to find other resources you're interested in.
One of these "hacks" would be, if you know the path to the JAR file(s), you could read from them using JarFile (or even just ZipFile).
.getClassLoader().getResource("otherresources")
Untested. Once you have the 'directory', use it to get back to the archive itself.
Establish a ZipInputStream to the archive.
Call getNextEntry() until null and add the entries matching the required location to an expandable list (e.g. ArrayList).
Construct an URL from the location of the archive and ZipEntry.getName()
Of course, I would normally suggest creating a list of the target resources when making the archive, then including that list at a known location in the archive. But the above might suffice for this use case.
I have created a few classes in Java and have combined them into a single package in the 'org' namespace. How do I access them from RingoJS?
Must I copy the package into the 'src/org' directory in Ringo or do I have to modify the classpath dynamically from the script?
I finally figured out how to access custom Java classes in RingoJS. I must have been really stupid to have never seen it right in front of my eyes before.
In Rhino, to access custom Java classes you need to add them to your Java classpath. This can be tedious, especially when an end user without any knowledge about programming tries to install a CommonJS package which requires specific Java classes to be in the classpath. Correct me if I'm wrong. Package managers like Tusk might be able to do that for you, and I would really appreciate it if you would notify me about such a feature. However, as far as I know it's the end users responsibility to add the Java classes and/or jar files to the classpath.
RingoJS simplifies this a great deal. It provides a global function addToClasspath(pathName) which adds a JAR file or directory to the classpath. Thus we may have a Java package or JAR file in the root directory of the CommonJS package and use the addToClasspath function in the JavaScript file itself to automatically add it to the classpath. In addition, all JAR files in the RingoJS lib directory are included in the classpath by default. This simplifies matters a great deal.
For programming purposes you may add JAR files to the RingoJS lib directory. However, I wouldn't recommend it. To reduce coupling and keep the RingoJS lib directory clean (preventing future namespace problems); and to make installation for the end user easier, I suggest using the addToClassPath function. Perhaps it should be implemented in other Rhino-based CommonJS implementations as well.
This one I couldn't find a proper answer.
I have 2 folders. One is called 'src', where
my java source code is located. The other one
is called 'srcGenerated' and has a set of files
created by a code generator. srcGenerated is a
superset of src.
I want to use both folders as my build path on
Eclipse. The problem is the duplicated files in
srcGenerated. Since there is no way to supress
the generation of files that are already in src,
my question is, how to delete the duplicated files
in srcGenerated based on the existing set of files
from src.
Ant or Powershell script preferred.
May not be the easiest one, but this is what I though of.
You can exclude Java files from srcGenerated that you don't want to be included while building.
Here you can exclude all the files that you don't want to be built. I know, it's painstaking if there are hundreds of conflicting Java files. But it definitely works.