"jar:file" vs. only "file:" reference when retrieving resources in Java - java

This question is related to my question
Jetty 11.0.11 - 404 on html file in \src\main\webapp\static - maven embedded fat jar
What --EXACTLY-- does "jar:file" mean as a Java resource reference, vs. just "file:"?
And how is that influenced by the operating system ran under?
E. g. using this resource reference in Jetty webserver, in Windows with Oracle JDK 17, files are found as resources and parsed by Jetty webserver:
file:///D:/Projects/verdi_2/target/classes/static/,AVAILABLE}{file:/D:/Projects/verdi_2/target/classes/static}
Using this resource reference in Jetty webserver, in Ubuntu Linux 20.04 LTS with Oracle JDK 17, NO files are found and nothing can be parsed by Jetty webserver:
jar:file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar!/static
Is there a difference in how a Linux version of JDK interprets "jar:file" vs. how a Windows version of the JDK interprets "jar:file"?
EDIT: The related issue is the Jetty webserver apparently can no longer serve resources directly out of a JAR file it is itself embedded in. This is now a GitHub bug ticket at https://github.com/eclipse/jetty.project/issues/8549

file: is the beginning of a general file url. jar:file: is that for a jar file particularly, with a view to referring (usually) to a particular entry in a jar. Here's an example you can run (obviously with your own jar url) where you can save an entry as a file (given by the parameter to the app)
import java.nio.file.Paths;
import java.nio.file.Files;
import java.net.URL;
public class JarUrl {
public static void main(String[] args) {
try {
URL url = new URL("jar:file:root.jar!/root/a/b.txt");
Files.copy(url.openStream(), Paths.get(args[0]));
}
catch(Throwable t) {
t.printStackTrace();
}
}
}

What --EXACTLY-- does "jar:file" mean as a Java resource reference, vs. just "file:"?
You're mischaracterising the URL a little bit. The string until the first : decides the 'scheme' of a URL, so, the pertinent question is: How does jar: work. The file: part is a smaller aspect of a sub-part of the jar bit.
How does jar: work
The format is jar:(URL-of-jar)!(path-inside-jar)
Where URL-of-jar is itself a valid URL (and file: is just one way to do that. So is http, for that matter), and path-inside-jar is not a URL but a path.
The meaning is: First, resolve the 'URL-of-jar' URL. This gets you a jar file. Then, open the jar file, and retrieve the resource at the stated path.
So, to pull this one apart:
jar:file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar!/static
The jar is located at URL file:/usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar and the resource it is referring to is the /static resource inside the jar found at the given URL.
How does file: work
That's not java-specific; file: is a generally available URL scheme. You can even type it in a web browser. The more general URL formatting scheme is scheme://server/resource, but with file:, server doesn't apply (it is by definition local to the system you are on), so usually its put as file:///foo, i.e. - an empty 'server' part. Because 3 slashes is a drag to type, I guess, file:/resource is allowed by some 'URL parsers', including java's in this regard, so, file:/usr/... simply maps straight to a local folder: /usr/src/verdi/verdi-12-JDK-etc, as in, if you type ls /usr/src/verdi/verdi-12-JDK17-jar-with-dependencies.jar on the command line on your system, it would show a result (and if it does not, this URL would fail to find anything).
And how is that influenced by the operating system ran under?
It isn't. file URLs are a general concept that work on any platform. Of course, /usr/src/verdi/etc is never going to work correctly on a windows platform. Or on anybody else's machine. The problem isn't "Oh no! This won't run on another OS!". The problem with file URLs, especially absolute ones, is "Oh no! This will not run on any machine other than this one!".
file:///D:/Projects
I've explained the triple slashes earlier. This is the standard windows 'scheme' for how to stick paths in file URLs: Always forward slashes (even though windows normally uses backslashes), and treat the disk letter as if it is a 'drive' in the 'root': /D:/Project is URL-ese for:
D:
cd \Project
There is no difference in OS at all - file: URLs are handled by 'interpret this file URL the way any file URL would be interpreted on this machine'.

The answer to the related question
Jetty 11.0.11 - 404 on html file in \src\main\webapp\static - maven embedded fat jar
which prompted this post is in the long series of posts beneath this GitHub issue for jetty:
https://github.com/eclipse/jetty.project/issues/8549
In essence, eventually I had to first clean up my Maven pom.xml (see this thread for the discussion and for links to a pom.xml example that is compliant with Maven Shade plugin and Jetty 11.0.11 requirements and standards) then at the end of the day hardcode a link to the JAR file to find the HTML, JS, etc. resources Jetty was to serve out as a webpage. Also put in a conditional where, on compiling, I need to specify if the code will run "in-IDE" (in my case, Netbeans 14) or "in-JAR" - e. g. in a detached JRE elsewhere than the Netbeans 14 IDE.
Also dropped using the Jetty WebAppContext class and started rendering web content out of a normal ServletContextHandler.
Hopefully this may help someone upgrading Jetty from Jetty 9.xxx to 11 and finding that it all falls apart.
For details as to why they changed so much, see the GitHub link (the last few entries are apropos.)
The github discussion also contains full working source code (startJettyc method) that solved the issue of getting a 404 in a detached, non-IDE modality where the JAR was being run in an JRE separate from an IDE.
Stefan

Related

Reach External Resource With ClassLoader.getResourceFromStream on WebSphere

I'm working with a local WebSphere server configured in IntelliJ Idea, and the application I'm working on is using a third-party library that loads a properties file with:
ThirdPartyClass.class.getClassLoader().getResourceAsStream(fileNameParameter);
It uses the default bootstrapClassLoader.
I've been instructed to make sure the properties file is in a config directory so that it can be edited without deploying a code change. My project looks something like this:
ProjectName
Configs
my.properties
src
java (sources root)
packages, .java files, etc
main (resources root)
schemas, web docs, etc
I have tried several of paths to make it work but it always returns null. Since I initially thought it was reaching from within the third party library package, I tried adding several ..\'s to the file path, but then I learned that this method loads from the classpath, so I pulled a
String test = System.getProperty("java.class.path");
and upon inspection, my classpath is all made up of websphere directories and jars within them:
C:\Users\me\Programs\IBM\AppServer\profiles\AppSrv01/properties
C:\Users\me\Programs\IBM\AppServer\AppSrv01/properties
and several jar files in C:\Users\me\Programs\IBM\AppServer/lib/
So just as a test I stuck the file in C:\Users\me\Programs\IBM\AppServer\AppSrv01/properties, then tried to grab it with just its file name (my.properties), but still couldn't reach it. I've also tried moving the file into the src directory and the main directory, but no matter what I do it just can't seem to find the file.
I'm aware that this method is typically used to grab resources from within a jar file, but from my understanding it seems like it should be possible to reach my file from outside of one as long as it's in a directory in the classpath... but apparently not since that didn't work.
I have the absolute path on my hard drive and will have said path on the server; is there a way to derive the path that ClassLoader.getResourceFromStream() wants with that info? Failing that, is there some obvious mistake I'm making with the resource url?
I think your fileNameParameter simply needs to start with / to indicate that it is in the root level of the classpath. Otherwise it will be searched relative to the class it is loaded from, i.e. the package of ThirdPartyClass in your example.

Java Find the absolute path of dependent jar

I have Jar file which dependency on another project jar. Both are thin jars and are at same location. 1st jar has manifest file which list second jar in its class-path property.
In 1st jar I am launching second jar as a process using ProcesBuilder class in java. To do so I need absolute path of second jar. In 1st jar i have class XClient
If I do XClient.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
i am getting absolute path of 1st jar. Then I can split and add the name of second jar(hard-coded) to build the absolute path
In second jar I have class XServer
If I do
XServer .class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
Its throws exception
I am not sure if I am doing the right approach but my goal is very clear I wanted to get the absolute path to the dependent jar.
Please help
I tried to use the same approach (but used File file=new File(this.getClass().getProtectionDomain().getCodeSource().toUri()) instead of getPath()) but this can fail in different ways:
when the class is inside a jar the File object points to the jar instead the folder the jar is in - so an if(file.isFile()) file=file.getParentFile(); is needed to get the directory instead of the jar file
when the jar file is loaded by something other than the usual URLClassLoader (last time I tried was back in 1.8 - and I only know that since Jigsaw the main classloader can't be cast to an URLClassLoader anymore) this may will return some unspecified result, if at all, so actual behaviour depends on the very system setup - wich can make it difficult to debug when used on a remote system not under your control
UNC paths (Windows shares) are error prone by themselfs - adding another layer on top of it (java) just add a lot of potential other pitfalls you all have to test and debug - wich often ends up you tell the client what to use and how to setup instead of design your code to follow the java principle: "write once, compile once, run everywhere" (btw: this also applies even if you "mount" a network share so you can address it by a local drive letter instead of a remote network path - but this even causes problems when you try to link two machines where one is a clone of the other)
as already mentioned as comment: "it doesn'T work" is not a usefull or meaningfull description - if you get an error message (in this case as you mentioned an exception stacktrace) post it along with the code wich produced it (if accessible)
How I solved my problem? I just ask the user for the directory / file by a swing JFileChooser. Yes, this isn't fool proof and maybe not the best way - but it works as swing still ships with SE JVM (instead of FX).
If you want to find a path use Class.getResource() and let java do the work, pretty much like crypto: don'T do your own.
Aside from all that: Your mentioned "usecase" doesn'T require what you try to do. You said that the server is already in the classpath - so it gets loaded on startup and you can access the XServer class. The easiest way instead of forking another process is to just run it in another thread. If you know wich class has the main (the manifest of the server.jar will tell you) and you can access it in classpath just do something like this:
Thread serverThread=new Thread(new Runnable()
{
public void run()
{
String[] args=Arrays.asList("required", "parameters");
XServer.main(args);
}
});
serverThread.start();
If no paramters required you can just pass an empty String array. As main() should not throw Exceptions (at least no checked ones) no exception should be needed.
Before all those comments are thrown at me: Yes, I am very well aware of possible issues with such approach like classpath issues (same classname in same packagename but different versions) and such it may be more feasible than try to figure out the absolute path and launch a fork / sub process.
Also: Starting another process may require to interact with its streams (provide required input into child process inputstream and read the child process outputstream and errorstream - otherwise the forked process may can "hang" as it waits for the pipelines to get cleared. It's a pain in the but to debug that kind of issue if it's not your own code and you can have a profiler and debugger attached to it to figure out why all just suddenly stopped to work.
If you really want to (I don't think there's any requirement forcing a "you need to") launch your server along with the client do it with a launch script outside of java but with os level stuff.

tomcat6 sqlnet encryption failure

Recently inherited an old Java codebase that makes use of tomcat6 with apache and am attempting to set up a dev environment. I'm getting an ORA-12649 code ("Unknown Encryption or Data Integrity algorithm") when calling DriverManager.getConnection() via the front-end JSP logon screen.
There are a number of things that do not make sense:
we have implemented encryption (via settings in slqnet.ora) on the target db which runs Oracle 11gR2, and it works with the production version of the same codebase; it also works with incoming sqldeveloper connections, etc.; basically, there have been no problems with the encryption implementation on the db side
the development codebase is exactly the same as the production codebase (at this point in time)
the development tomcat6 installation is the same version as the production installation
if I point the connector at another db that does NOT implement encryption, the authorization succeeds with a valid username and password
After extensive reading through Oracle documentation and forums, tomcat6 documentation (specifically the convoluted way it handles the CLASSPATH variable) I have come up empty.
My hunch is that the tomcat6 install on the dev system is not referencing the correct jar files even though I have the ojdbc6.jar file in the tomcat install lib folder. According to Oracle, having ojdbc6.jar available should just work when it comes to implementing this type of encryption from a thin client, which is how this tomcat app is implemented.
Here's how encryption is being implemented on the client side; this compiles without error:
...
prop.setProperty(
OracleConnection.CONNECTION_PROPERTY_THIN_NET_ENCRYPTION_LEVEL,
AnoServices.ANO_REQUIRED);
prop.setProperty(
OracleConnection.CONNECTION_PROPERTY_THIN_NET_ENCRYPTION_TYPES,
"( " + AnoServices.ENCRYPTION_AES256 + "," +
AnoServices.ENCRYPTION_3DES168 + "," +
AnoServices.ENCRYPTION_AES192 + " )");
// require the use of the SHA1 algorithm for data integrity checking
prop.setProperty(
OracleConnection.CONNECTION_PROPERTY_THIN_NET_CHECKSUM_LEVEL,
AnoServices.ANO_REQUIRED);
prop.setProperty(
OracleConnection.CONNECTION_PROPERTY_THIN_NET_CHECKSUM_TYPES,
"( " + AnoServices.CHECKSUM_SHA1 + " )");
...
Here are the pertinent lines in the sqlnet.ora file on the db side, which is known to work with multiple clients:
SQLNET.ENCRYPTION_SERVER=required
SQLNET.ENCRYPTION_TYPES_SERVER=(AES256,AES192,3DES168)
sqlnet.crypto_checksum_server=required
sqlnet.crypto_checksum_types_server=(SHA1)
This is the db url being used in tomcat's web.xml file in the application directory:
jdbc:oracle:thin:#<my_db_name>:1521:<my_db_sid>
My context.xml file implements the 'allowLinking' feature, not sure if that makes a difference but it is non-standard so I'm including that detail. This allows me to provide a symlink in tomcat's <webapps> folder that points to the proper location in my repo. The directory permissions are OK since tomcat is serving up pages from that location.
<Context path="/<my_app_name>" allowLinking="true">
It seems that the ojdbc6.jar file referenced in the CLASSPATH during servlet compilation was different than the ojdbc6.jar file referenced by tomcat6. My working assumption was that contents of any file named "ojdbc6.jar" is static, but apparently I was wrong! (Can anyone confirm if Oracle does indeed release different versions of the file named "ojdbc6.jar"? I couldn't find any evidence that they do).
After much more time spent on this, I became convinced that the issue lay in the driver version being used even though the jar filename was "ojdbc6.jar" across all instances. So, I used md5sum to confirm that both .jar files were the same, and sure enough, they were not! So I re-downloaded ojdbc6.jar from Oracle and copied it to both locations where it was needed, recompiled my servlet classes, and restarted tomcat6. No more errors on login over the encrypted connection.
So it appears that someone had the grand idea of renaming an older/invalid version of an ojdbcX.jar file to ojdbc6.jar in the past. I don't even want to know why. :)

JNLP loads Sigar native libs via file: but not http:

I have an app that is using jnlp as the launcher. It uses the Sigar libraries which require dynamically loaded native libraries for platform specific code.
For purposes of debugging this I have two JNLP files, one that references the codebase using file: urls and the other using http: urls. The http urls point to localhost apache which is properly serving the files. I can watch JNLP download them during its launch sequence via apache logs so I know the files are getting to my app properly.
Here are the two codebase tags
codebase="file:/Users/siberian/Documents/workspace_mnis/MNIS/localhost/"
href="file:/Users/siberian/Documents/workspace_mnis/MNIS/localhost/minis.jnlp"-->
and
codebase="http://localhost/"
href="http://localhost/mnis.jnlp"
If I double click the file: version it works fine. If I load it via my browser it works fine.
If I double click or browser load the http: version it fails to find the dynamic libraries with this error:
JNLPClassLoader: Finding library liblibsigar-universal64-macosx.dylib.dylib
[AWT-EventQueue-0] DEBUG Sigar - no libsigar-universal64-macosx.dylib in java.library.path
org.hyperic.sigar.SigarException: no libsigar-universal64-macosx.dylib in java.library.path
Now, the interesting to note is that file that it says it cant find
liblibsigar-universal64-macosx.dylib.dylib
Note the prefix extra 'lib' and postfix extra '.dylib'.
There are notes on the Sigar/vmware forums about similar problems with no solutions.
The core question is, why is this acting differently in a file: context vs an http: context?
Also of note, I have unsigned and resigned all of my files, there are no signature errors that I can see.
There are hints at an answer here: Java Webstart with Tibco Native Libs
But it works in a file: context which makes me think something else is wrong.
Also: JaNeLa tells me all is fine
JNLP and Sigar classloaders do not play well together. This was pieced together but works well in both Windows and Mac environments. The VMWare forums HINT at an answer like this but no one has put it all together. For JNLP you need to specifically do a loadLibrary based on your architecture. In a non-JNLP context Sigar handles this transparently but JNLP breaks that somehow, requiring this manual platform selection.
Just put this method into your class and call it BEFORE you call new Sigar() and it should work properly. This solutions requires the commons-lang library. You can easily extend this for linux and other alternate platform support.
private static void preloadSigar() {
String arch = System.getProperty("os.arch");
String libName;
if (SystemUtils.IS_OS_WINDOWS) {
if (arch.equalsIgnoreCase("x86"))
libName = "sigar-x86-winnt";
else
libName = "sigar-amd64-winnt";
} else if (SystemUtils.IS_OS_MAC_OSX) {
if (arch.startsWith("i") && arch.endsWith("86"))
libName = "sigar-universal-macosx";
else
libName = "sigar-universal64-macosx";
} else {
throw new RuntimeException("Unrecognized platform!");
}
System.setProperty("org.hyperic.sigar.path", "-");
System.loadLibrary(libName);
}

ImageMagick/IM4J FileNotFoundException

I am trying to use IM4J (a Java wrapper for ImageMagick) to create thumbnails of JPEGs and it is my first experience (ever) with both libraries. Please note that this is a hard requirement handed to me by my tech lead (so please don't suggest to use anything other than an IM4J/ImageMagick) solution - my hands are tied on the technology choice here!
I am getting a FileNotFoundException on the and convert command which tells me I don't have one of these libraries (or both) setup correctly.
On my computer, here is my directory structure:
C:/
myApp/
images/ --> where all of my JPEGs are
thumbnails/ --> where I want ImageMagick to send the converted thumbnails to
imageMagickHome/ --> Where I downloaded the DLL to
ImageMagick-6.7.6-1-Q16-windows-dll.exe
...
In my Java project, I make sure that the IM4J JAR (im4java-1.2.0.jar) is on the classpath at runtime. Although I am required to use the 1.2.0 version of IM4J, I have the liberty to use any version of ImageMagick that I want. I simply chose this version because it seemed like the most current/stable version for my Windows 7 (32-bit) machine. If I should use a different version, please send me a link to it from the ImageMagick downloads page in your answer!
As for ImageMagick, I just downloaded that EXE from here and placed it in the folder mentioned above - I didn't do any installation, wizard, MSI, environment variable configuration, etc.
Then, in my Java code:
// In my driver...
File currentFile = new File("C:/myApp/images/test.jpg"); --> exists and is sitting at this location
File thumbFile = new File("C:/myApp/thumbnails/test-thumb.jpg"); --> doesnt exist yet! (destination file)
Thumbnailer myThumbnailer = new Thumbnailer();
myThumbnailer.generateThumbnail(currentFile, thumbFile);
// Then the Thumbnailer:
public class Thumbnailer
{
// ... omitted for brevity
public void generateThumbnail(File originalFile, File thumbnailFile)
{
// Reads appConfig.xml from classpath, validates it against a schema,
// and reads the contents of an element called <imPath> into this
// method's return value. See below
String imPath = getIMPathFromAppConfigFile();
org.im4java.core.IMOperation op = new Operation();
op.colorspace(this.colorSpace);
op.addImage(originalFile.getAbsolutePath());
op.flatten();
op.addImage(thumbnailFile.getAbsolutePath());
ConvertCmd cmd = new ConvertCmd();
cmd.setSearchPath(imPath);
// This next line is what throws the FileNotFoundException
cmd.run(op);
}
}
The section of my appConfig.xml file that contains the imPath:
<imPath>C:/myApp/imageMagickHome</imPath>
Please note - if this appConfig.xml is not well-formed, our schema validator will catch it. Since we are not getting schema validation errors, we can rule this out as a culprit. However, notice my file path delimiters; they are all forward slashes. I did this because I was told that, on Windows systems, the forward slash is treated the same as a *nix backslash, in reference to file paths. Believe it or not, we are developing on Windows
machines, but deploying to linux servers, so this was my solution (again, not my call!).
IM4J even acknowledges that Windows users can have trouble sometimes and explains in this article that Windows developers might have to set an IM4JAVA_TOOLPATH env var to get this library to work. I tried this suggestion, created a new System-wide environmental variable of the same name and set its value to C:\myApp\imageMagickHome. Still no difference. But notice here I am using backslashes. This is because this env var is local to my machine, whereas the appConfig.xml is a config descriptor that gets deployed to the linux servers.
From what I can tell, the culprit is probably one (or more) of the following:
I didn't "install" the ImageMagick EXE correctly and should have used an installer/MSI; or I need to add some other environmental variables for ImageMagick (not IM4J) itself
Perhaps I still don't have IM4J configured correctly and need to add more environmental variables
Could be the Windows/*nix "/" vs. "" issue from my appConfig.xml file as mentioned above
I'm also perplexed as to why I'm getting a FileNotFoundException on a file named "convert":
java.io.FileNotFoundException: convert
I assume this is a batch/shell file living somewhere inside the IM4J jar (since the only thing I downloaded for ImageMagick was the EXE). However, if I extract the IM4J jar I only see classes inside of it. I see "script generator" classes, so I assume these kick off before my cmd.run(op) call and create the convert file, and maybe that's what I'm missing (perhaps I need to manually kick off one of these generators, like CmdScriptGenerator prior to executing my Thumbnailer methods. . Or, maybe my download is incomplete.
Either way, I'm just not versed enough with either library to know where to start.
Thanks for any help with this.
Run the 'ImageMagick-6.7.6-1-Q16-windows-dll.exe' installer first to install the imagemagick libraries. Then make sure your environment path includes the location of the installed binaries ('convert.exe', 'mogrify.exe', etc)
Make sure u have Set the environment-variable IM4JAVA_TOOLPATH.

Categories

Resources