URL to nested Jar files / looking for workaround

S

Simon

How do I safely get a JarURLConnection from a URL that points to a jar file,
and, in particular, if the jar file itself is located inside another jar file?

The following does not (always) work:

URL url = Foo.class.getResource("/my/jar.jar");
URL jarUrl = new Url(url, "jar:" + url + "!/";
JarURLConnection jarConnection =
(JarURLConnection)jarUrl.openConnection();

It works in some cases. If I have a folder "/build/" containing my classes and
resources and run my app, the value of url.toString() is

file:/build/my/jar.jar

and the value of jarUrl.toString() is

jar:file:/build/my/jar.jar!/

With this, everything works fine. However, if I package my app into a jar, say
"dist/app.jar", the value of url.toString() is

jar:file:/dist/app.jar!/my/jar.jar

and jarUrl becomes:

jar:jar:file:/dist/app.jar!/my/jar.jar!/

This seems to confuse JarURLConnection resulting in a MalformedURLException,
complaining "no !/ in spec". Note: The exception does *not* occur in the
constructor of the URL, but in openConnection, more precisely,

at java.net.URL.<init>(URL.java:601)
[...]
at java.net.JarURLConnection.parseSpecs(JarURLConnection.java:161)

Here is the code of JarURLConnection (1.6.0_06-b02):

/*
* REMIND: we don't handle nested JAR URLs
*/
if (separator == -1) {
throw new MalformedURLException("no !/ found in url spec:" + spec);
}
// This is line 161, in which it breaks. It is not the
// throws statement two lines above:
jarFileURL = new URL(spec.substring(0, separator++));


In fact, the comment inside the source seems to answer my question, saying "we
don't handle nested JAR URLs". However, I'm puzzled as to what the "REMIND"
refers to. I haven't found this documented anywhere.

In case it really does not work, what would be a suitable workaround?

My application is this: I have an application which can be extended using
resources bundled into jars. If the user adds another bundle, he enters a URL
(pointing to a jar) and everything works fine (as in the 1st example). My
standard bundle, however, is a jar inside the application jar, and this is where
I want it to be. Of course, I could ship my app as several jars, but that would
mean that I have to figure out some way for the app to locate the standard
bundle on the user's file system, which is something completely transparent to
the application up to now.

Any ideas?

Cheers,
Simon
 
J

John B. Matthews

[experimental results elided]
In fact, the comment inside the source seems to answer my question,
saying "we don't handle nested JAR URLs". However, I'm puzzled as to
what the "REMIND" refers to. I haven't found this documented
anywhere.

In case it really does not work, what would be a suitable workaround?

My application is this: I have an application which can be extended
using resources bundled into jars. If the user adds another bundle,
he enters a URL (pointing to a jar) and everything works fine (as in
the 1st example). My standard bundle, however, is a jar inside the
application jar, and this is where I want it to be. Of course, I
could ship my app as several jars, but that would mean that I have to
figure out some way for the app to locate the standard bundle on the
user's file system, which is something completely transparent to the
application up to now.

One approach I've seen is to copy the canonical resource (in this case a
jar) to a configuration directory in user.home. The user is directed to
add new jars to this directory, where the application can discover them
at well-defined times: on startup or in response to a component event.
The URL feature would continue to be used to identify jars in other
locations.

If it's appropriate, the application can save changes to the working
copy according to the user's preferences, but the original would remain
available should the copy be lost.
 
A

Andrew Thompson

[experimental results elided]
My application is this: I have an application which can be extended
using resources bundled into jars. If the user adds another bundle,
he enters a URL (pointing to a jar) and everything works fine
....
One approach I've seen is to copy the canonical resource (in this case a
jar) to a configuration directory in user.home. ...

Here is an example of that, using the classname
as the basis for the pathname of the config. file.
<http://sdnshare.sun.com/view.jsp?id=2305>
 
J

John B. Matthews

Andrew Thompson said:
[experimental results elided]
My application is this: I have an application which can be extended
using resources bundled into jars. If the user adds another bundle,
he enters a URL (pointing to a jar) and everything works fine
...
One approach I've seen is to copy the canonical resource (in this
case a jar) to a configuration directory in user.home. ...

Here is an example of that, using the classname
as the basis for the pathname of the config. file.
<http://sdnshare.sun.com/view.jsp?id=2305>

I enjoyed your fine article, but I'm afraid the listing was mangled in
transit. Any alternative source?
 
A

Andrew Thompson

...the listing was mangled in
transit. Any alternative source?

I noticed the listing of *all* the code posts
was mangled, and thought it was because Mozilla
never loaded the last stylesheet (the pages
never show fully loaded here).

(shrugs) Anyway..
Raw
<http://pscode.org/test/AppStore.java>
<http://pscode.org/test/AppStoreDemo.java>

Formatted
<http://pscode.org/fmt/sbx.html?url=/test
%2FAppStoreDemo.java&col=2&fnt=2&tab=2>
<http://pscode.org/fmt/sbx.html?url=/test
%2FAppStore.java&col=2&fnt=2&tab=2&ln=0>
 
J

John B. Matthews

Andrew Thompson said:
I noticed the listing of *all* the code posts
was mangled, and thought it was because Mozilla
never loaded the last stylesheet (the pages
never show fully loaded here).

I see what you mean. I'm intrigued by the run-time error, unique to
each post, as if the web site were compiling and executing the posted
code.

Sweet! Thank you.

[...]
 
S

Simon

John said:
[experimental results elided]
In fact, the comment inside the source seems to answer my question,
saying "we don't handle nested JAR URLs". However, I'm puzzled as to
what the "REMIND" refers to. I haven't found this documented
anywhere.

In case it really does not work, what would be a suitable workaround?

My application is this: I have an application which can be extended
using resources bundled into jars. If the user adds another bundle,
he enters a URL (pointing to a jar) and everything works fine (as in
the 1st example). My standard bundle, however, is a jar inside the
application jar, and this is where I want it to be. Of course, I
could ship my app as several jars, but that would mean that I have to
figure out some way for the app to locate the standard bundle on the
user's file system, which is something completely transparent to the
application up to now.

One approach I've seen is to copy the canonical resource (in this case a
jar) to a configuration directory in user.home. The user is directed to
add new jars to this directory, where the application can discover them
at well-defined times: on startup or in response to a component event.
The URL feature would continue to be used to identify jars in other
locations.

Thank you John and Andrew.

That is probably a good practice for the add-ons. However, it does not actually
solve my original problem, which was to access a jar directly from inside
another jar.

Let's assume we accept the following two restrictions, which I actually consider
benefits:

1) The application does not need any installation other than unpacking an
archive, basically containing the main jar, library jars linked via the main
jar's Manifest, and possibly some default add-ons. These files go to a directory
called the application root.

2) The application does not make a best guess approach to figure out what the
application root is.

Following your approach, I don't see how I can copy the add-on jar to user.home.
If I could, I could as well access it from the application root. I see three
options that do work:

A) Access the jar inside the main jar via getResource() and extract it to a temp
file or to user.home. Work on the copy. Ugly because it duplicates files and
wastes disk space.

B) Write a custom URLHandler that can access nested jars. Elegant, but probably
difficult. Is this jar:URL!/ syntax standard at all?

C) Place a directory "/path/to/standard-add-on" inside the main jar, and also
include "/path/to/standard-add-on/META-INF/MANIFEST.MF". Then, implement a
custom class loader that delegates to the parent class loader, prefixing
everything with "/path/to/standard-add-on" to fool my add-on loader into
believeing it is accessing a jar rather than a subtree of the main jar. That
would probably do the job, although not being overly beautiful as well.

Cheers,
Simon
 
J

John B. Matthews

[QUOTE="Simon said:
[experimental results elided]
My application is this: I have an application which can be
extended using resources bundled into jars. If the user adds
another bundle, he enters a URL (pointing to a jar) and everything
works fine (as in the 1st example). My standard bundle, however,
is a jar inside the application jar, and this is where I want it
to be. Of course, I could ship my app as several jars, but that
would mean that I have to figure out some way for the app to
locate the standard bundle on the user's file system, which is
something completely transparent to the application up to now.

One approach I've seen is to copy the canonical resource (in this
case a jar) to a configuration directory in user.home. The user is
directed to add new jars to this directory, where the application
can discover them at well-defined times: on startup or in response
to a component event. The URL feature would continue to be used to
identify jars in other locations.

Thank you John and Andrew.

That is probably a good practice for the add-ons. However, it does
not actually solve my original problem, which was to access a jar
directly from inside another jar.

Let's assume we accept the following two restrictions, which I
actually consider benefits:

1) The application does not need any installation other than
unpacking an archive, basically containing the main jar, library jars
linked via the main jar's Manifest, and possibly some default
add-ons. These files go to a directory called the application root.

2) The application does not make a best guess approach to figure out
what the application root is.

Following your approach, I don't see how I can copy the add-on jar to
user.home. If I could, I could as well access it from the application
root. I see three options that do work:

A) Access the jar inside the main jar via getResource() and extract
it to a temp file or to user.home. Work on the copy. Ugly because it
duplicates files and wastes disk space.[/QUOTE]

Permit me to demur: If the copy may be modified by the user in the
normal course of operation, the original is preserved against corruption
of the copy. This is the foundation of a common support ritual, "delete
the preferences and see what happens." Of course, if the original is
unmodified, your point is well taken.
B) Write a custom URLHandler that can access nested jars. Elegant,
but probably difficult. Is this jar:URL!/ syntax standard at all?

Good question. FWIW, I see that the '!' (bang) token is not reserved in
a URI, per RFC 2396.
 
S

Simon

A) Access the jar inside the main jar via getResource() and extract
Permit me to demur: If the copy may be modified by the user in the
normal course of operation, the original is preserved against corruption
of the copy. This is the foundation of a common support ritual, "delete
the preferences and see what happens." Of course, if the original is
unmodified, your point is well taken.

Good point. I'll think about that though I believe that the user is very
unlikely to modify the copy in my specific case.

Cheers,
Simon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top