Run a JAR from Anywhere

By: Vlad Patryshev

Abstract: This program lets you run a JAR stored not only on a disk drive, but at any URL.

The Problem

URLs have been a part of our life for several years already - but somehow they still remain second-class locators. Why is it so that I can easily view a text or an image, be it on my computer or anywhere on the Internet, but to run a program I have to copy it first?

That was my thought when I was looking for the appropriate tool on the web. Java Web Start? It is good, but too big, way too big. Have to set it up, restart my computer, write manifests, configs... it may be good for a large application, but how about just a self-contained program, say a Java program in a JAR? There is also WebRun, a GNU project. It is almost what I would need, but not exactly. What did I need? I needed, instead of sending out new versions of my tool, to send a program that would run my tool from my Web server, inside our intranet. Others, I thought, would want the same, would they not?

The Program

So here it is, a very small program that can run JARs, like this:

java -jar runajar.jar <jar location> <program arguments>

where <jar location> can be a file path or a URL.

For instance,

runajar http://www.patryshev.com/snippets/Java2Demo.jar

will run the beautiful Java2D demo that I have stored on my site.

The Code

The class Runajar consists of two methods, Main that does the whole thing, and pipe that helps me copy from one stream to another. I was tempted to use java.nio, which would be more efficient, but then I would be limited to JDK 1.4... no hurry.

Method pipe is primitive and, I think, self-explaining: it has two modes, blocking and non-blocking. In blocking mode it reads blocks of bytes from the input stream and sends them to output stream while the input stream can provide bytes; in non-blocking mode this operation stopes when available() returns 0.

The program itself first copies the file at the specified URL into its private temporary directory, using pipe in blocking mode; then it starts Java with the JAR just loaded, and pipes the process' input, output and error with the appropriate streams from System, waiting for the process to finish; then it returns whatever exit code the JAR returned.

The Candy

With JBuilder, you can easily produce native executables for all the platforms JBuilder supports. Go to Wizards->Native Executable Builder... and add native executable to your code. Then you can use the binary, runajar instead of "java -jar runajar.jar".

The Link

The archive with the source code and executables is stored on CodeCentral, id=19015; below is the code itself.


package runajar;

import java.net.*;
import java.io.*;

/**
 * <p>Title: run a jar <u>from</u> anywhere in the world</p>
 * <p>Description: java -jar runajar.jar <url or path of the jar to run> <arguments>
 *             or: runajar <url or path of the jar to run> <arguments></p>
 * <p>Company: Borland</p>
 * @author vpatryshev
 * @version 1.0
 *
 */

public class Runajar {

  static final int BLOCK_SIZE = 65500; // bytes
  static byte[] buf = new byte[BLOCK_SIZE];
  static final int SLEEP_UNIT = 333;   // msec
  static final String TMP_DIR = "/tmp/runjar";

  public static void main(String[] args) {
    try {
      if (args.length > 0) {
        String urlstring = args[0]; // jar url string
        File here = new File(".");
        File localcopy = new File(urlstring);

        if (!localcopy.exists()) {
          URL url = new URL(urlstring);
          String filename = urlstring.substring(urlstring.lastIndexOf('/') + 1); // jar name
          File root = new File(here.getAbsolutePath()); // is not it something?
          while (root.getParent() != null && root.getParent() != root.getCanonicalPath()) {
            root = new File(root.getParent()); // that's the way to find the root
          }
          File tmpdir = new File(root, TMP_DIR);

          if (!tmpdir.exists()) tmpdir.mkdirs();

          localcopy = new File(tmpdir, filename); // local copy of the jar
          InputStream is = new BufferedInputStream(url.openStream());
          OutputStream os = new BufferedOutputStream(new FileOutputStream(localcopy));
          pipe(is, os, true);
          os.close();
          is.close();
        }
        String javaexec = System.getProperty("java.home") + File.separator +
                  "bin" + File.separator +
                  "java";
        String cmd =  javaexec + " -jar " + localcopy.getAbsolutePath();

        for (int i = 1; i < args.length; i++) {
          cmd += " "" + args[i] + """;
        }
        Process process = Runtime.getRuntime().exec(cmd, null, here);
        int result = 0;

        for (boolean isRunning = true; isRunning;) {
          Thread.sleep(SLEEP_UNIT);
          try {
            result = process.exitValue();
            isRunning = false;
          } catch (IllegalThreadStateException ie) {}

          try {
            pipe(System.in, process.getOutputStream(), false);
            pipe(process.getErrorStream(), System.err, false);
            pipe(process.getInputStream(), System.out, false);
          } catch(Exception e) {}
        }
        System.exit(result);
      }
    } catch (Exception e) {
      System.err.println(e);
    }
  }

  static void pipe(InputStream in, OutputStream out, boolean isBlocking) throws IOException {
    int nread;
    int navailable;
    int total = 0;
    synchronized (in) {
      while((navailable = isBlocking ? Integer.MAX_VALUE : in.available()) > 0 &&
            (nread = in.read(buf, 0, Math.min(buf.length, navailable))) >= 0) {
        out.write(buf, 0, nread);
        total += nread;
      }
    }
    out.flush();
  }
}


Server Response from: ETNASC03