tags:

views:

93

answers:

4

Hi, 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

A: 

Have you had a look at the URLClassloader class yet?

The JarClassloader here might be useful: http://www.java.happycodings.com/Other/code27.html

Thorbjørn Ravn Andersen
(As it happens `URLClassLoader` always caches for disk, for no particularly good reason.)
Tom Hawtin - tackline
+1  A: 

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");
+2  A: 

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.

simontuffs
+1  A: 

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 ([email protected]).   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});

    }

}

simontuffs