Getting list of files inside a Java applet's JAR - java

I'm trying to fetch list of files using a method that apparently works well with non-applet Java code.
I'm fully aware it's messy; I'm just trying to get this to work for a school assignment. (I'm no fan of Java.)
CodeSource src = MemoryButtonHelper.class.getProtectionDomain().getCodeSource();
if (src == null) {
throw new Exception();
}
URL jar = src.getLocation();
System.out.println("Loading from " + jar);
JarFile zf=new JarFile(jar.toString()); //jar.openStream());
final Enumeration<JarEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
final JarEntry ze = entries.nextElement();
if(ze.getName().endsWith(".jpg") || ze.getName().endsWith(".png"))
{
System.out.println("Adding " + ze.getName());
slikeList.add(ze.getName());
}
}
zf.close();
Unfortunately, I'm getting a security exception.
java.security.AccessControlException: access denied (java.lang.RuntimePermission getProtectionDomain)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.Class.getProtectionDomain(Class.java:2106)
at hr.tvz.programiranje.java.deseti.helpers.MemoryButtonHelper.getSlike(MemoryButtonHelper.java:75)
… ad nauseam …
According to Java Console, exception appears to occur before the println("Loading from " + jar).
This is a bonus point assignment which specifically says that we must fetch the list of images from the JAR file. Since this is my first encounter with the applets, I'm not sure what I can do to fetch the list of images.

..we must fetch the list of images from the JAR file.
Who put them in there in the first place? If the answer is 'we did', the solution is simple.
Include a list of image path/names at a known location (e.g. images/imagelist.txt) in the Jar.
Obtain a reference to the list using getResource(String).
Read the list (probably using a line reader).
Apparently they want me to list the contents of the jar as-is, without extra metadata.
OK. If you can form an URL to (and thereby an InputStream from) the Zip file, it is possible to establish a ZipInputStream from it. This should work whether the URL is to a Zip/Jar cached on the local file-system or still at the server.
Get an URL to the Jar.
Establish a ZipInputStream from the URL.
Iterate the entries using getNextEntry() until null
Examine each one for a potential match and if it does, add it to an ArrayList.
Of course, you'll still need signed & trusted code to call for the protection domain.
Images are definitely in the same JAR
To get an URL to the Jar, try this (untested). Let's assume the applet is com.our.BookingApplet.
Obtain an URL to the Jar in which the class resides, using
URL urlToApplet = this.getClass().getResource("/com/our/BookingApplet.class")
String[] parts = urlToApplet.toString().split("!") will provide two parts, the first will be a String representation of the Jar URL.
Use that String to establish an URL, then use the URL as described in the previous update.

Thanks go to Andrew Thompson for his excellent answer! Definitely upvote his answer instead (or in addition) to this one, since without his help, I wouldn't be able to figure this out.
Here is the portion of the code which I came up with to use to fetch list of .jpg and .png files inside the JAR file. Note that you probably need to change the name of the package where MemoryGame class is stored, as well as change the name of the class itself.
List<String> slikeList = new ArrayList<String>();
URL urlToApplet = MemoryGame.class.getResource("/com/whoever/whatever/gui/MemoryGame.class");
String[] parts = urlToApplet.toString().split("!");
String jarURLString = parts[0].replace("jar:", "");
System.out.println("Loading from " + jarURLString);
URL jar = new URL(jarURLString);
URLConnection jarConnection = jar.openConnection();
JarInputStream jis = new JarInputStream(jarConnection.getInputStream());
JarEntry je = jis.getNextJarEntry();
while(je != null)
{
System.out.println("Inspecting " + je);
if(je.getName().endsWith(".jpg") || je.getName().endsWith(".png"))
{
System.out.println("Adding " + je.getName());
slikeList.add(je.getName());
}
je = jis.getNextJarEntry();
}
In case you wonder, slike means images in Croatian since a lot of variables named in the exercise specification are in Croatian.

Only "Signed" applets are allowed to access file system. If the jar file which you are reading from is located on your local file system, you will need to sign the applet first.
See this link for more information on how to sign applet.

Related

How to read the bytes of an image resource located inside a nested JAR?

I am struggling while trying to read the bytes of a PNG image bundled with the resources of a JAR. The file is located in the src/main/resources directory.
Here is my code so far:
byte[] bytes = {};
final InputStream defaultImageStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/defaultLogo.png");
new DataInputStream(defaultImageStream).readFully(bytes);
The code is executed on a Wildfly 12 server, located in a JAR included in the EAR as an EJB.
It seems than instead of retrieving the resource I asked for, getResourceAsStream returns the enclosing jar:
How can I get that image?
Additional info:
I tried both with an exploded and non-epxloded JAR in the EAR. Same results.
The path to the resource seems correct. Prefixing it by "/resources" ends in the method returning NULL.
I tried using the Class' classloader instead of the thread context's one. Same results.
I envisioned going through all the entries of the enclosed JAR myself, but this both seems overkill and difficult: since I have a JarInputStream and no JarFile, how would I read the data corresponding to an entry?
I think your code is working as intended. Looking at the DataInputStream instance isn't going to tell you much. Look at the content, I think it is the image you want.
You're thinking correctly, the JarInputStream would server the purpose for you.
Your code should be something like below--
try {
JarInputStream jarIS = new JarInputStream(new FileInputStream(
"jarfilePath"));
JarEntry entry = null;
while ((entry = jarIS.getNextJarEntry()) != null) {
String name = entry.getName();
if (name.endsWith("defaultLogo.png")) {
System.out.println( "You got the PNG File"+entry.getAttributes().toString() );
//Now handle your stream as per your requirement.
}
}
} catch (Exception e) {
}

Restlet Directory - default index.html file not being served

I have:
Directory webdir = new Directory(getContext(), "clap://class/webapp");
webdir.setDeeplyAccessible(true);
router.attach("",webdir);
This works when serving all files in the directory by name.
However, it should serve index.html when you visit "/" and it doesn't. I've tried all combinations of path, extra routers etc etc and it's still not working.
When you visit "/" you get a 200 response and an application/octet-stream content type. The response is otherwise empty. The getIndexName on Directory assures me it's index
I've also tried getMetadataService().addExtension("html", MediaType.TEXT_HTML, true); to help it to pick up the index.html file but to no avail and also set the accept header in the request to text/html.
ETA: it is the same (unsolved) problem that's described here: http://restlet-discuss.1400322.n2.nabble.com/Serving-static-files-using-Directory-and-CLAP-from-a-jar-td7578543.html
Can anyone help with this? It's driving me nuts.
After a bit of fiddling I have this workaround in place now, but I'd rather not redirect if possible:
Redirector redirector = new Redirector(getContext(), "/index.html", Redirector.MODE_CLIENT_PERMANENT);
TemplateRoute route = router.attach("/",redirector);
route.setMatchingMode(Template.MODE_EQUALS);
The behaviour is caused by the way the ClapClientHelper class identifies a target as a file or directory. The workaround solution is to replace the ClapClientHelper class with another nearly identical one called say JarClapClientHelper. Copy the source code from ClapClientHelper and change the following snippet in the handleClassLoader method.
// The ClassLoader returns a directory listing in some cases.
// As this listing is partial, it is of little value in the context
// of the CLAP client, so we have to ignore them.
if (url != null) {
if (url.getProtocol().equals("file")) {
File file = new File(url.getFile());
modificationDate = new Date(file.lastModified());
if (file.isDirectory()) {
url = null;
}
//NEW CODE HERE
} else if (url.getProtocol().equals("jar")) {
try {
JarURLConnection conn = (JarURLConnection) url.openConnection();
modificationDate = new Date(conn.getJarEntry().getLastModifiedTime().toMillis());
if (conn.getJarEntry().isDirectory()) {
url = null;
}
} catch (IOException ioe) {
getLogger().log(Level.WARNING,
"Unable to open the representation's input stream",
ioe);
response.setStatus(Status.SERVER_ERROR_INTERNAL);
}
}
}
Now you need to load this helper instead of the default one.
(Thanks to #Thierry Boileau.)
There are two ways, one that does not require any code, and one that is programmatic.
The first one is to let the ServiceLoader find your service:
add a file called META-INF/services/org.restlet.engine.ClientHelper to your solution.
this file should have a single line of text: the full name (including the package) of your helper class
The second one is to add the helper programmatically:
Engine.getInstance().getRegisteredClients().add(0, new JarClapClientHelper(null));
Try using the WAR protocol:
Directory directory = new Directory(getContext(), "war:///");
directory.setIndexName("index.html");
router.attach("/", directory);
Pay attention to the triple slash to identify the war package root, otherwise you will get NullPointerException.
(In my case I have a Maven project and my index file is put on the root of the generated war package).

Glassfish - uploading images - doing it right

I am on latest glassfish (3.1.2) - so no need for apache FileItem and no bugs with getPart(). I read that the best practice on uploading images is saving them on the file system (see here for instance). I am editing already existing code - smelly at that - so I had the idea to do :
Part p1 = request.getPart("file");
System.out.println("!!!!!P1 : " + p1);
Prints :
!!!!!P1 : File name=DSC03660.JPG,
StoreLocation=C:\_\glassfish3\glassfish\domains\domain1\generated\jsp\elkethe\upload_7cb06306_138b413999a__7ffa_00000000.tmp,
size=2589152bytes, isFormField=false, FieldName=file
newlines mine. In the code people are doing :
if (request.getParameter("crop") != null) {
// get path on the server
String outputpath = this.getServletContext().getRealPath(
"images/temp/" + session.getId() + ".jpg");
// store photo
InputStream is = p1.getInputStream();
createPhoto(is, outputpath);
session.setAttribute("photo_path", "images/temp/" + session.getId()
+ ".jpg");
response.sendRedirect("cropping");
return;
}
Where
private void createPhoto(InputStream is, String outputpath) {
FileOutputStream os = null;
try {
os = new FileOutputStream(outputpath);
// write bytes taken from uploaded file to target file
int ch = is.read();
while (ch != -1) {
os.write(ch);
ch = is.read();
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
Helpers.close(os);
}
}
Now what happens is that the file is uploaded in the StoreLocation (???) on submitting the form so apparently all this p1.getInputStream() is for naught.
My questions are :
what is StoreLocation ? How tmp are those glassfish uploads ? Where are all those parameters set ? I did read BalusC' tutorial - but there is no mention of StoreLocation (google is not very helpful either).
What would be a more professional way of handling the situation - including keeping the photos outside the webroot - but using facilities glassfish provides (if it does provide) ?
Even p1 printing so nice escapes me (it does not seem to Override toString())
Interested in tips even in how should one rename the photos etc (is this sessionID thing Right ? - check also the time trick) :
if (request.getParameter("save") != null) {
long time = System.currentTimeMillis();
String path = "images/upload/" + session.getId() + time + ".jpg";
String outputpath = this.getServletContext().getRealPath(path);
// store photo
InputStream is = p1.getInputStream();
createPhoto(is, outputpath);
// etc
}
Good practice is to pick a path on the filesystem where photos will be uploaded. Often this path is programmed to be configurable via java system property (eg: by passing -Dcom.mycompany.uploadPath=/path/to/photos/dir system property on JVM arguments).
You can also use java system propeties to find environment specific path: user.dir, user.home etc. See System Properties on Java SE Tutorial. Or to use glassfish-relative path, see glassfish system properties.
Once you have reference to Part, it's just about doing file IO to copy the uploaded file into this upload path, eg:
Part part = // obtain part somehow..
String photoFileName = // build a file name somehow..
InputStream photoInputStream = part.getInputStream();
FileOutputStream photoOutputStream = new FileOutputStream(System.getProperty("com.mycompany.uploadPath") + File.separator + photoFileName);
IOUtils.copy(photoInputStream, photoOutputStream);
// close streams here...
Code above uses apache IOUtils for convenience but feel free to write your own copy method. You should also add exception handling method
What is StoreLocation ? How tmp are those glassfish uploads ? Where are all those parameters set ?
StoreLocation is just the the java.io.File object for the FileItem's
data's temporary location on the disk. Resides in javax.servlet.context.tempdir which defaults to %GLASSFISH_HOME%\domains\domain1\generated\jsp\webApp. Those uploads are as tmp as anything (The lifetime of the file is tied to the lifetime of the FileItem instance; the file will be deleted when the instance is garbage collected - from here). Haven't yet managed to change the value of javax.servlet.context.tempdir programmatically (comment please) - it is the tempdir property of the sun-web-app element of the sun-web.xml.
What would be a more professional way of handling the situation - including keeping the photos outside the webroot - but using facilities glassfish provides (if it does provide) ?
Well a more professional way is to Use Part.write() to move the file to the desired location. Due to glassfish implementation though you can't supply an absolute path to write - a chore. I asked here.
As to where to save the file : https://stackoverflow.com/a/18664715/281545
That is for saving the file - to serve it from a location outside the app you need to define "alternatedocroot" properties in the sun-web.xml (or glassfish-web.xml).
Even p1 printing so nice escapes me (it does not seem to Override toString())
Oh yes it does
Interested in tips even in how should one rename the photos etc (is this sessionID thing Right ? - check also the time trick)
No it is not - I tend towards File#createTempFile() - anyway this is a different question asked here

JarInputStream: getNextJarEntry always returns null

I have an I18n helper class that can find out the available Locales by looking at the name of the files inside the application's Jar.
private static void addLocalesFromJar(List<Locale> locales) throws IOException {
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
String name = entry.getName();
// ...
}
}
Currently, this isn't working - jar.getNextJarEntry() seems to always return null. I have no idea why that's happening, all I know is that url is set to rsrc:./. I have never seen that protocol, and couldn't find anything about it.
Curiously, this works:
class Main {
public static void main(String[] args) {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
System.out.println(entry.getName());
}
}
}
In this version, even though there is practically no difference between them, the url is correctly set to the path of the Jar file.
Why doesn't the first version work, and what is breaking it?
UPDATE:
The working example really only works if I don't use Eclipse to export it. It worked just fine in NetBeans, but in the Eclipse version the URL got set to rsrc:./ too.
Since I exported it with Package required libraries into generated JAR library handling, Eclipse put its jarinjarloader in my Jar so I can have all dependencies inside it. It works fine with the other settings, but is there any way to make this work independently of them?
Another question
At the moment, that class is part of my application, but I plan to put it in a separate library. In that case, how can I make sure it will work with separate Jars?
The problem is the jarinjarloader ClassLoader that is being used by Eclipse. Apparently it is using its own custom rsrc: URL scheme to point to jar files stored inside the main jar file. This scheme is not understood by your URL stream handler factory, so the openStream() method returns null which causes the problem that you're seeing.
This answers the second part of your question about separate jars - not only will this work, it's the only way that it will work. You need to change your main application to use separate jars instead of bundling them all up inside the main jar. If you're building a web application, copy them into the WEB-INF/lib directory and you're fine. If you're building a desktop application, add a relative path reference in the META-INF/MANIFEST.MF to the other jars, and they will automatically be included as part of the classpath when you run the main jar.
The code may or may not result into the jar file where I18n resides. Also getProtectionDomain can be null. It depends how the classloader is implemented.
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
about the rsrc:./ protocol, the classloader is free to use whatever URL they please (or name it for that matter)
try this out, you might get lucky :)
URL url = getClass().getResource(getClass().getSimpleName()+".class");
java.net.JarURLConnection conn = (java.net.JarURLConnection) url.openConnection();
Enumeration<JarEntry> e = conn.getJarFile().entries();
...
and good luck!
Eclipse's jarinjarloader loads everything using the system classloader and it never knows what jar file it was loaded from. That's why you can't get the jar URL for a rsrc: url.
I suggest storing the list of locales in a file in each application jar, e.g. META-INF/locales. Then you can use ClassLoader.getResources("META-INF/locales") to get the list of all the files with that name in the classpath and combine them to obtain the full list of locales.
I use System.getProperty("java.class.path") for getting the location of the jar. I do not know if that makes a difference. I have not explored the ProtectDomain path so I cannot help you there, sorry. As for multiple jars, just iterate through those jar file also.

How to walk through Java class resources?

I know we can do something like this:
Class.class.getResourceAsStream("/com/youcompany/yourapp/module/someresource.conf")
to read the files that are packaged within our jar file.
I have googled it a lot and I am surely not using the proper terms; what I want to do is to list the available resources, something like this:
Class.class.listResources("/com/yourcompany/yourapp")
That should return a list of resources that are inside the package com.yourcompany.yourapp.*
Is that possible? Any ideas on how to do it in case it can't be done as easily as I showed?
Note: I know it is possible to know where your jar is and then open it and inspect its contents to achieve it. But, I can't do it in the environment I am working in now.
For resources in a JAR file, something like this works:
URL url = MyClass.class.getResource("MyClass.class");
String scheme = url.getProtocol();
if (!"jar".equals(scheme))
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
JarURLConnection con = (JarURLConnection) url.openConnection();
JarFile archive = con.getJarFile();
/* Search for the entries you care about. */
Enumeration<JarEntry> entries = archive.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().startsWith("com/y/app/")) {
...
}
}
You can do the same thing with resources "exploded" on the file system, or in many other repositories, but it's not quite as easy. You need specific code for each URL scheme you want to support.
In general can't get a list of resources like this. Some classloaders may not even be able to support this - imagine a classloader which can fetch individual files from a web server, but the web server doesn't have to support listing the contents of a directory.
For a jar file you can load the contents of the jar file explicitly, of course.
(This question is similar, btw.)
The most robust mechanism for listing all resources in the classpath is currently to use this pattern with ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author of ClassGraph.)
List<String> resourceNames;
try (ScanResult scanResult = new ClassGraph()
.whitelistPaths("com/yourcompany/yourapp")
.scan()) {
resourceNames = scanResult.getAllResources().getNames();
}
I've been looking for a way to list the contents of a jar file using the classloaders, but unfortunately this seems to be impossible. Instead what you can do is open the jar as a zip file and get the contents this way. You can use standard (here) ways to read the contents of a jar file and then use the classloader to read the contents.
I usually use
getClass().getClassLoader().getResourceAsStream(...)
but I doubt you can list the entries from the classpath, without knowing them a priori.

Categories

Resources