I have a jar file (say app.jar) in a certain location available as a file/stream from an http server. The problem is that app.jar is itself a frequently updated jar file being updated regularly at the server.
I have another jar file (say load.jar) which is simply downloadable by any user.
At runtime, the user runs the load.jar, which is required to load the app.jar in a separate process keeping the app.jar in memory (no cacheing to disk), then execute the app.jar and terminate itself (load.jar).
Is it possible to be done? Any help is highly appreciated.
Thanks in advance.
Regards,
KT
---------------------------Update.
Hi,
Thanks all for the reply. However, I guess I have not given a complete picture.
An image link text is attached depicting the scenario.
4 jars (the actual common executors) are hosted on a central server. These are updated frequently (probably 3-4 times a day initially down to once a day eventually).
A local server at one of the member is hosted which is initialised.
The launcher on the local server downloads all the 4 binaries and keeps it in memory - no cacheing to disk. These jar files are self-sufficient and are not dependent on any library on the "Local Server" - they in fact invoke jars from the "Local Server".
The "client" jar eventually is hosted with the "Local Server" and forwarded to its clients on demand via webapp by the "Local Server".
Also, the Launcher needs to exit by invoking the downloaded Main jar from the server in a separate JVM.
Regards,
KT
OK. Before I put the link to the szegedi article into my previous answer (honest), I prototyped a jar-launcher that could handle URLs. Like the author said, it wasn't hard, probably isn't complete. Attached below. I think this is 1/3 of what you need. Your user says (something like):
java -jar load.jar http://localhost:8080/app.jar
load.jar has two roles: (1) invoke JarLauncher (below) as its main class (2) serve app.jar on a localhost port (before invoking the JarLauncher). load.jar therefore reads its arguments to figure out which app to run.
Finally (the hard bit): you have to make URLClassLoader not hit the temporary disk. As the szegedi article said, that isn't easy. Alternatively, you can write your own network-aware classloader that doesn't disk-cache, like my first suggestion loading the URL into memory as a byte-stream from a URL connection, decoding it as a JarInputStream, and satisfying calls to loadClass/findResource from that in-memory stream.
You have quite a lot of work ahead of you. Good luck.
Apologies for the size of the license text, but it lets you do what you like with the code as long as you don't blame me (BSD).
--simon.
/*
* One-JAR(TM) (http://www.simontuffs.com/one-jar). Copyright (c) 2004-2010,
* P. Simon Tuffs (simon#simontuffs.com). All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of P. Simon Tuffs, nor the names of any contributors,
* nor the name One-JAR may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Including this file inside the built One-JAR file conforms with these terms.
*/
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarInputStream;
/**
* Programmatic equivalent of what happens when you say "java -jar <jar-file.jar>".
* A better solution to debugging/running Jar files inside an IDE.
* #author simon
*
*/
public class JarLauncher {
public static URL getURL(String string) throws MalformedURLException {
try {
return new URL(string);
} catch (MalformedURLException x) {
return new URL("file:" + string);
}
}
public static void main(String args[]) throws Exception {
if (args.length < 1) {
System.out.println("Usage: java [-Dkey=value...] JarLauncher <jar-file.jar>");
System.exit(1);
}
String jar = args[0];
args = Arrays.copyOfRange(args, 1, args.length);
List<URL> urls = new ArrayList<URL>();
// Main jar on the URL path.
// Dig out the main class.
urls.add(getURL(jar));
URL jarurl = urls.get(0);
JarInputStream jis = new JarInputStream(jarurl.openStream());
String main = jis.getManifest().getMainAttributes().getValue("Main-Class");
// OK to split on space, because embedded space is %20
String classpaths[] = jis.getManifest().getMainAttributes().getValue("Class-Path").split(" ");
for (String classpath: classpaths) {
urls.add(getURL(classpath));
}
URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[0]));
Class<?> cls = loader.loadClass(main);
Thread.currentThread().setContextClassLoader(loader);
Method m = cls.getMethod("main", new Class[]{new String[0].getClass()});
m.invoke(null, new Object[]{args});
}
}
I will suggest have a look at http://jcloader.sourceforge.net/
This has better flexibility and is quite feature rich.
JarClassLoader jcl = new JarClassLoader();
//Loading classes from different sources
jcl.add("myjar.jar");
jcl.add(new URL("http://myserver.com/myjar.jar"));
jcl.add(new FileInputStream("myotherjar.jar"));
jcl.add("myclassfolder/");
//Recursively load all jar files in the folder/sub-folder(s)
jcl.add("myjarlib/");
JclObjectFactory factory = JclObjectFactory.getInstance();
//Create object of loaded class
Object obj = factory.create(jcl, "mypack.MyClass");
Further Edit:
I scanned the web and came across this excellent article which may also have a bearing on your question: http://www.szegedi.org/articles/remotejars.html
In which case most of what I wrote below is irrelevant, but I'll leave it anyway just in case.
Edit:
Ok, I think I'm getting a better picture of your requirements. Requirements are tricky though, so let me tell you what I think you're trying to do, and then I'll see what solutions that suggests. I probably will get this wrong, then we do it again.
Requirements:
A distributed caching mechanism for serving WebStart applications. The applications (.jars) change on a regular basis. They are self-contained. You want to have a local-cache able to serve the Webstart jar files obtained from a central server, keeping them in memory. I do not understand your requirements for Exit.
Solutions?
What you may be looking for is a web-server that can host a webapp that will read updated application jars into memory, then serve them to the webstart launcher. You will need to use a web-server such as Jetty/Tomcat, and write a simple webapp to run under it. The webapp will poll the central server for application updates, and will make the application jars available to webstart through URLs which it serves. Polling the central server probably makes more sense than having the central server push new applications to the local servers for reasons of firewall and scalability.
I don't know of any off-the-shelf webapps that do this, there may be one.
But like I said, I probably still don't get this. Requirements: got to love them.
I'm going to make some assumptions in this answer, based on your question. It sounds like you want to cache the "app.jar" from the remote machine, for the lifetime of the "load.jar" on the local machine, in order to allow isolate "load.jar" from changes to "app.jar" during its lifetime. This seems like a good idea: I would expect that if you use an URLClassLoader to bring "app.jar" into the "load.jar" space, then an update mid-way through execution could wreak havoc.
It also sounds like you don't want to have "load.jar" make a disk-based copy of the "app.jar" -- I think this is a reasonable goal: who wants to have old jar files scattered around on the machine? who wants permission issues relating to trying to write temporary files?
Given these goals, you need to find or implement a classloader that makes a snapshot of "app.jar" when "load.jar" first hits a class inside it. This isn't hard: I had to do something similar in my One-JAR JarClassLoader, which loads Jar files from inside Jar files. During startup it loads all found bytes into memory, then resolves classes on demand using that memory store. While possibly less efficient than lazy-loading, it's fast, and stable, and would solve your problem.
Unfortunately, One-JAR does not allow network (or file) resources to be cached in this manner. It uses delegation through a normal URL classloader, which would make it not cache your remote resources.
I suggest you take a look at the CVS repository: http://one-jar.cvs.sourceforge.net/viewvc/one-jar/one-jar/src/com/simontuffs/onejar/JarClassLoader.java?revision=1.58&view=markup
around line 678. Don't be daunted by the size of this class, its a bunch of special cases to handle, well, special cases.
If I had to create something from scratch, I would subclass URLClassLoader, override the loadClass, getResource, and findResource methods (see for example line 281 where One-JAR does this for its own class-loader inversion needs), put in bytecode caching by loading the entire "app.jar" into memory as a byte array hashmap indexed by class/resource name keys (this is One-JAR again), and then you'd be cached in memory.
I hope this helps.
--simon.
Have you had a look at the URLClassloader class yet?
The JarClassloader here might be useful: http://www.java.happycodings.com/Other/code27.html
Related
I am running this code:
public static void main(String[] args) throws IOException, PGPException {
InputStream privateKeyRings = new FileInputStream("/path/to/file/secring.gpg");
PGPSecretKeyRingCollection pgpSecretKeyRings =
new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyRings), new JcaKeyFingerprintCalculator());
}
And I get this error:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: org.bouncycastle.openpgp.PGPPublicKeyRing found where PGPSecretKeyRing expected
at org.bouncycastle.openpgp.PGPSecretKeyRingCollection.<init>(Unknown Source)
at test.main(test.java:36)
Therefor I inspected the file secring.gpg by running
gpg --show-keys --no-default-keyring secring.gpg
Which gives me a list of keys
sec# rsa4096 2013-02-21 [SC]
37B...
uid xyz <xyz#xyz.com>
ssb# rsa4096 2013-02-21 [E]
sec# rsa4096 2013-02-14 [SC]
22C...
uid abc <abc#abc.com>
ssb# rsa4096 2013-02-14 [E]
pub rsa4096 2013-04-19 [SC]
5A1...
uid def <def#def.com>
sub rsa4096 2013-04-19 [E]
So as far as I can tell, the file contains secret keys, except for the last entry which seems to be a public key.
So my guess is that this is causing the error.
What is the best way to avoid the error?
Do I need to remove the public key from the file secring.gpg? If so, what is the command for this?
And is there another way to handle this scenario in the java code?
Yes that class supports only secret key 'rings' (note what Bouncy calls a 'keyring' is what GnuPG just calls a key, and what GnuPg calls a keyring is what Bouncy calls a 'collection').
I think the best way is indeed to fix the file. That file name secring.gpg was used by older versions of GnuPG for a file in OpenPGP format that contained only secret keys, not public, so whoever created that file either did something really weird and wrong or was trying to mislead and harm people. I don't think there ever was a command that could fix a public key in secring because that wasn't supposed to happen in the first place. In any case modern GnuPG no longer uses that file; it now uses a directory containing separate files in a libgcrypt-based format.
What you could do is use GnuPG to --import this file (import can handle both secret and public keys, because interchange can in various cases use either) and then --export-secret-keys the desired ones. If you set env var GNUPGHOME to a new (empty) directory and import to that, you can simply export everything valid from that directory and then delete it.
Alternatively you could write your own class which is like PGPSecretKeyRingCollection but ignores (skips) any PGPPublicKeyRing object(s). If the rest of your code, or any code you call to use this data, only uses the behavior of PGPSecretKeyRingCollection and doesn't depend on internals or exact identity -- which by OO doctrine it shouldn't -- you can just substitute your object. In older Java you can even extends the real class, so that it can be stored in variables etc. declared with the real type, but with recent Java (9 up) I think subclassing across modules is restricted and this may require --add-opens or somesuch -- I haven't looked into that area yet.
More ambitiously, Bouncy is opensource, so you could fork it and make this change, and any others you want. Of course you would need to set up a complete build environment for it, and periodically take at least the upstream changes that either provide new features you need or want or are security fixes -- in practice it's almost certainly easier to take all changes, and if something conflicts cross that bridge when you get to it.
Little background for context:
The application I support allows third parties to develop plugins that can leverage some of our functionality. We hand them our "externalAPI.jar"; they put it in their project, implement some interfaces, and build their own APK. We find the would-be plugin by asking the package manager for all installed applications and see if each has a "pluginclass.xml" in the assets directory. If it has that XML file, we anticipate its contents being the canonical path of a class that implements our ExternalPluginVX interface, and using a new PathClassLoader(ApplicationInfo.sourceDir, this.getClass().getClassLoader()), we load the class, create a new instance, and start using it.
The problem:
Sometimes third parties will put
compile files ("./libs/externalAPI.jar")
in their gradle files instead of the correct syntax:
provided files ("./libs/externalAPI.jar")
The result of course being things don't work properly. Sometimes they almost work, but then have unpredictability in their behavior - usually involving vicious crashes. Notably, since their APK is well-formed in its own right, and the XML file is there, we'll see the plugin, load the target class successfully, instantiate it successfully, and things go haywire from there when they try and reference back to us.
The question:
Is there a way for my application to check at runtime if the other application compiled our API classes into their APK instead of using provided files like they should have?
A viable solution is to use a DexFile.
Since I already have the ApplicationInfo.sourceDir, I can just construct a DexFile and iterate through its contents.
//this variable's value assigned by iterating through context.getPackageManager().getInstalledApplications(0)
ApplicationInfo pkg;
String interfaceTheyShouldntHave = ExternalPluginVX.class.getCanonicalName(); //"com.project.external.ExternalPluginVX"
DexFile dexFile = new DexFile(pkg.sourceDir);
Enumeration<String> entries = dexFile.entries();
while(entries.hasMoreElements()){
String entry = entries.nextElement();
if(entry.equals(interfaceTheyShouldntHave)){
Toast.makeText(ctxt, "Plugin \"" + pluginName + "\" could not be loaded. Please use 'provided files' instead of 'compile files'", Toast.LENGTH_LONG).show();
return;
}
}
I can't seem to figure out why I can't create a directory from a Java program FOR A SPECIFIC LOCATION.
The specifics are - the folder of my computer is shared on the network.
Code:
File xmlDirectory = new File(sXMLOutputPath);
/*
* TODO: if multiple threads arive at the 1st if, if will evaluate to true,
* then 1st thread would create directory, and the 2nd being .01 sec later, will fail
* to create directory and have exception
* SOLUTION: Provide additional if exists, so that 2nd thread will recognize that it
* was created.
*/
if (!xmlDirectory.exists()){
if (!xmlDirectory.mkdirs()){
if (!xmlDirectory.exists()){
throw new BillAdminException("Failed to create xml directory: " +
sXMLOutputPath);
}
}
}
This is a server side code
In summary - if I share my foder C:\folder\etc - and pass it as JVM options to the program, the server side program "appends" \xml\333\333.xml" to it, and is supposed to create that xml file on MY PC. First it creates a structire C:\folder\etc\xml\333\, and then it creates 333.xml. It fails creating C:\folder\etc\xml\333 if the C:\folder\etc is passed as shared location in the form "\myMachine\etc", but works OK if I make that structure on some other machine "\OtherMachine\etc". If I pass it as "C:\folder\etc" (absolute, not shared form) it will work fine, creating directory and file on the server machine where code is executed. I need it created on my machine (client). What am I doing wrong when sharing folder.
P.S - this functionality worked about 2 month ago. However, the folder properties could have been tempered with since then. Not the java code, though.
P.S 2: this is not the only shared folder I pass from JVM options. There are 2 others, but used for reading (not creating subfolders)
Thanks, for help
P.S 3: The error I get is:
Failed to create xml directory: \\myMachine\etc\xml/333/
What I smell fishy, is that the slash before "333" is reversed. But, there were no changes in the code, so the same would have been happening before.
I'm using Java 1.4.2 and Debian 6.0.3. There's a shared Windows folder in the network, which is correctly mounted to /mnt/share/ via fstab (e.g. it's fully visible from OS and allows all operations) using CIFS. However, when I try to do this in Java:
System.out.println(new File("/mnt/share/").listFiles().length)
it would always return 0, meaning File[] returned by listFiles is empty. The same problem applies to every subdirectory of /mnt/share/. list returns empty array as well. Amusingly enough, other File functions like "create", "isDirectory" or even "delete" work fine. Directories mounted from USB flash drive (fat32) also work fine.
I tested this on 2 different "shared folders" from different Windows systems; one using domain-based authentication system, another using "simple sharing" - that is, guest access. The situation seems weird, since mounted directories should become a part of a file system, so any program could use it. Or so I thought, at least.
I want to delete a directory in my program, and I currently see no other way of doing it except recursive walking on listFiles, so this bug becomes rather annoying. The only "workaround" I could think of is to somehow run an external bash script, but it seems like a terrible solution.
Edit: It seems this is 1.4.2-specific bug, everything works fine in Java 6. But I can't migrate, so the problem remains.
Could you suggest some workaround? Preferably without switching to third-party libs instead of native ones, I can't say I like the idea of rewriting the whole project for the sake of single code line.
Since Java 1.2 there is method File.getCanonicalFile(). In your case with mounted directory you should use exactly this one in such style:
new File("/mnt/share/").getCanonicalFile().listFiles()
So, two and half years later after giving up I encounter the same problem, again stuck with 1.4.2 because I need to embed the code into obsolete Oracle Forms 10g version.
If someone, by chance, stumbles onto this problem and decides to solve it properly, not hack his way through, it most probably has to do with (highly) unusual inode mapping that CIFS does upon mounting the remote filesystem, causing more obscure bugs some of which can be found on serverfault. One of the side-effects of such mapping is that all directories have zero hard-link count. Another one is that all directories have "size" of exactly 0, instead of usual "sector size or more", which can be checked even with ls.
I can't be sure without examining the (proprietary) source code, but I can guess that Java prior to 1.5 used some shortcut like checking link count internally instead of actually calling readdir() with C, which works equally well for any mounted FS.
Anyway, the second side-effect can be used to create a simple wrapper around File which won't rely on system calls unless it suspects a directory is mounted using CIFS. Other versions of list and listFiles functions in java.io.File, even ones using filters, rely on list() internally, so it's OK to override only it.
I didn't care about listFiles returning File[] not FileEx[] so I didn't bother to override it, but is should be simple enough. Obviously, that code can work only in Unix-like systems having ls command handy.
package FSTest;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
public class FileEx extends File
{
public FileEx(String path)
{
super(path);
}
public FileEx(File f)
{
super(f.getAbsolutePath());
}
public String[] list()
{
if (this.canRead() && this.isDirectory())
{
/*
* Checking the length of dir is not the most reliable way to distinguish CIFS mounts.
* However, zero directory length generally indicates something unusual,
* so calling ls on it wouldn't hurt. Ordinary directories don't suffer any overhead this way.
* If this "zero-size" behavior is ever changed by CIFS but list() still won't work,
* it will be safer to call super.list() first and call this.listUsingExec if returned array has 0 elements.
* Though it might have serious performance implications, of course.
*/
if (this.length() > 0)
return super.list();
else
return this.listUsingExec();
}
else
return null;
}
private String[] listUsingExec()
{
Process p;
String command = "/bin/ls -1a " + this.getAbsolutePath();
ArrayList list = new ArrayList();
try
{
p = Runtime.getRuntime().exec(command);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
for (String line = reader.readLine(); line != null; line = reader.readLine())
{
if (!line.equalsIgnoreCase(".") && !line.equalsIgnoreCase(".."))
list.add(line);
}
String[] ret = new String[list.size()];
list.toArray(ret);
return ret;
}
catch (IOException e)
{
return null;
}
}
}
Is there a way to convert JAR lib into JAR standalone?
I need to find a standalone java executable that convert PDF into TIFF and I've found these JARs: http://www.icefaces.org/JForum/posts/list/17504.page
Any ideas?
Easiest might be to create another Jar with a Main() entry point, and then just use the java.exe executable to run it:
e.g.
> java.exe -cp MyJarMain.jar;MyPDFJar.jar com.mydomain.MyMain myPDF.pdf
Where MyMain is a class with a Main static method.
You'll need something with a main entry point to pass in and interpret some command line arguments (myPDF.pdf in my made-up example)
You could do an assembly (are you using maven?) and make sure the Main-Class entry in the manifest.mf points to the main class.
Since there is no main-Method, you have to write one, or write a whole new class to call the class/method TiffConver.convertPDF .
The question is, how you're going to use it. From the command line, you need no executable jar. From the Gui, maybe you want to pass a file to be converted by drag and drop? Then you should take the parameter(s) passed to main as Input-PDF-Names (if they end in .pdf) and pass the names iteratively to TiffConverter, for "a.pdf b.pdf" =>
TiffConver.convertPDF ("a.pdf", "a.tiff");
TiffConver.convertPDF ("b.pdf", "b.tiff");
TiffCoverter will silently overwrite existing tiffs, so check that before or change the code there - this is clearly bad habit, and look out for more such things - I didn't.
/*
* Remove target file if exists
*/
File f = new File(tif);
if (f.exists()) {
f.delete();
}
Maybe you wan't to write a swing-wrapper, which let's you choose Files interactively to be converted. This would be a nice idee, if no filename is given.
If the user passes "a.pdf xy.tiff" you could rename the converted file to xy, as additional feature.
Without a main-class, however, a standalone jar would be magic.
However, building a native executale is almost always a bad idea. You loose portability, you don't profit from security- and performance improvements to the JVM or fixed bugs. For multiple programs you need always an independend bugfix, which you might have to manage yourself, if you don't have a package-management as most linux distros have.
after clearing some questions:
public static void main (String [] args) {
if (args.length == 1 && args[0].endsWith (".pdf")) {
String target = args[0].replaceAll (".pdf$", ".tif");
convertPDF (args[0], target);
}
}
This method you put into TiffConvert. It will allow you to convert a simple pdf-File, and generate a tif-File with the same basename but ending in .tif, silently overwriting an existing one of the same name.
I guess you now need to know how to start it?