/* $Id: WebRun.java,v 2.7 2002/01/19 16:13:21 mks Exp $ */ /* * You can place this in any package you wish. It does not * depend on the package name and even finds its own name * at run time for usage information. */ // package ORG.sinz.tools; /** *
* Copyright 1998-2001 Michael Sinz. All rights reserved. | ||
---|---|---|
*
|
* ||
* *
* The front end is run as the main Java class using any Java 1.1 or * later runtime environment (RT, JRE, or JDK). * It may be compiled as part of a package or outside of packages. It * does not depend on its package name in any way other than it is * the location where we place our Java tools. *
* The code was specifically written in such a way as to only require * a single class file (in addition to Java) to be on the user's system. * Thus some of the strange constructs to do the AWT windows for the * password entry and for the status display. *
* Since the user may not wish AWT interactions, WebRun does not require it. * If you do not ask for the authorization requester or for status window * display, WebRun will run without AWT interactions which means it can * run without any AWT requirements (such as without X on a Unix system). * See the -R switch below for details. *
* The syntax is simple:
* java [
jdk_options]
* [package.]WebRun
* [
webrun_options]
* your.class.name
* [
your_arguments]
*
or
* java [
jdk_options]
* [package.]WebRun url
*
* The first form provides for local configuration of what will run and all * of the options that may be involved. *
* The second form provides for a centeralized mechanism for configuring
* what the options are to WebRun. This also reduces the amount of possible
* local configuration error that a user may have. In the second form, WebRun
* will parse an HTML file looking for its own special <META>
* tags. WebRun will then execute each of those tags:
*
*
* <META NAME="WebRun" CONTENT="webrun_options your.class.name your_options">
*
* The second form must only have a single argument to the * WebRun class. All actual arguments will then be processed from * the WebRun meta tag. *
* You can try an example here. *
* Note: Some options are normally not shown to the user. These are specifically * hidden to reduce support issues in thin-client environments. However, they * can be useful as long as the person using them is available to support * them. These advanced options are displayed when -H is given. *
*
The options for WebRun are as follows: | |||
---|---|---|---|
parameter | description | ||
-h | Help - displays the usage information | ||
-R | Never put up a requester to get the user name and password | ||
-E | Do not put up a requester with the error codes | ||
-l URL | A URL to the directory where the class tree starts | ||
-j JarURL | A URL to a Jar file (you can use -j more than once | ||
-v | Verbose loading (stdout) | ||
-w | Verbose loading (window) | ||
Options only shown with '-H' | |||
-r | Put up a requester to get the user name and password | ||
-u user | User name for authorization | ||
-p password | Password for authorization | ||
-a authorization | The encoded authorization string | ||
-g user password | Generate an encoded authorization string | ||
-G | Put up a requester to get the user name and password and output the encoded authorization string | ||
WebRun was written by Michael Sinz. You can contact him at WebRun@Sinz.org.
* ** WebRun needs at least a Java 1.1 environment in order to operate. The Apple Macintosh OS Runtime for Java, * also known as MRJ provides the Java environment for the Macintosh OS. WebRun has been * tested with the Apple MRJ 2.1 releases and specifically with the MRJ 2.1.4, which is the newest * release available at this time. You can get the latest MRJ * from Apple at http://devworld.apple.com/java/. You will also want to get the JBindery tool * which is, unfortunately, only available in the MRJ SDK 2.1. It too is available at the Apple web site. *
* To install WebRun it is easiest to keep the binary file WebRun.zip and place that into the JBindery * classpath settings. You can also extract the WebRun.class file from the zip file and add just that * class to the classpath. (WebRun is all in a single class) *
* To then use WebRun you would put WebRun into the classname field in the JBindery. Then, * for the optional parameters you would fill in the parameters as needed to run WebRun. (See above * for details as to the options.) You would add the WebRun.zip file to the classpath in JBindery * and that should be all that is needed for WebRun. If your application needs special properties * added, you would also add those in the JBindery. One should normally not change the application * settings, such as the min/max heap sizes. The Apple MRJ does most of its memory operations in the * dynamic heap and thus does not need or want special min/max heap settings. *
* WebRun needs at least a Java 1.1 environment in order to operate. The * Sun Java Runtime Environment * for the 32-bit Microsoft Windows operating systems is a good place to start if you do not already have * Java on your Windows machine. *
* Once you have the binary for WebRun (WebRun.zip) and Java installed on your machine you can then make * shortcuts to run your various web based Java applications as follows: *
* Create a new shortcut by right-clicking into the desktop and selecting New->Shortcut. Enter into
* the command line "jrew.exe -cp [path_to_webrun.zip]\webrun.zip WebRun [arguments_to_webrun]
"
* See above for details as to the arguments to WebRun. If the jrew.exe command is not in your
* command path then you need to enter the complete path to the jre.exe command. If you are having problems
* or want STDOUT
available, you should replace jrew.exe with jre.exe in the
* above example. The jrew.exe runs as a Windows application with no console while the jre.exe
* runs as a Windows console application.
*
* WebRun needs at least a Java 1.1 environment in order to operate. You can * check this web page at Sun's Java site or * with your operating system vendor for details as to where to get the Java JRE or JDK for your system. *
* Add the WebRun.zip file or the WebRun.class file to your classpath as needed by your environment. * Due to the wide variety of operating environments on Unix systems I can not detail the steps needed * to setup an icon or shortcut to run WebRun. The command line options and features are all detailed * above and apply to all systems. */ public class WebRun extends java.lang.ClassLoader implements java.lang.Runnable,java.awt.event.WindowListener,java.awt.event.ActionListener { /** * The size of our workspace buffer during file loading... */ private static final int BUF_SIZE=2048; /** * This is the number of milliseconds the error window will * stay up if the user does not close it first. */ private final static int ERROR_TIMEOUT = 60 * 1000; /** * This is the URL we will be looking for classes at */ private String BaseURL=null; /** * The authorization string to be used. If null, no * authorization is needed. */ private String Authorization=null; /** * This flag, if true, disables load-time authorization * requesters. */ private boolean NoAuthorizationRequester=false; /** * Create a class loader. *
This is private so that only we can create one... * * @param url The URL to which the class loader is to connect. If null, the * class loader will not attempt to load from a URL. * @param authorization The authorization token (basic authorization) for the * URL connection. If null, no authorization will be attempted. * @param noRequest Set to true to prevent authorization requesters. * @param showStatus True to output class loading progress. * @param windowStatus If true and showStatus is true, the loading progress * will be displayed in an AWT window. */ private WebRun(String url,String authorization,boolean noRequest,boolean showStatus,boolean windowStatus) { super(); BaseURL=url; Authorization=authorization; NoAuthorizationRequester=noRequest; loadStatusDisplay=showStatus; loadStatusDisplayWindow=windowStatus; if (loadStatusDisplay && loadStatusDisplayWindow) { java.lang.Thread display=new java.lang.Thread(this); display.setDaemon(true); display.start(); } } /** * Create a class loader. *
This is private so that only we can create one... * * @param url The URL to which the class loader is to connect. If null, the * class loader will not attempt to load from a URL. * @param authorization The authorization token (basic authorization) for the * URL connection. If null, no authorization will be attempted. * @param showStatus True to output class loading progress. * @param windowStatus If true and showStatus is true, the loading progress * will be displayed in an AWT window. */ private WebRun(String url,String authorization,boolean showStatus,boolean windowStatus) { this(url,authorization,false,showStatus,windowStatus); } /** * Create a class loader. *
This is private so that only we can create one... * * @param url The URL to which the class loader is to connect. If null, the * class loader will not attempt to load from a URL. * @param authorization The authorization token (basic authorization) for the * URL connection. If null, no authorization will be attempted. * @param showStatus True to output class loading progress. */ private WebRun(String url,String authorization,boolean showStatus) { this(url,authorization,showStatus,false); } /** * Create a class loader. *
This is private so that only we can create one... * * @param url The URL to which the class loader is to connect. If null, the * class loader will not attempt to load from a URL. * @param authorization The authorization token (basic authorization) for the * URL connection. If null, no authorization will be attempted. */ private WebRun(String url,String authorization) { this(url,authorization,false); } /** * Create a WebRun class - This version is mainly so * that we can do such things as call the getAuth method. * See the static getAuthorization method. *
This is private so that only we can create one... */ private WebRun() { this(null,null); } /** * Create a thread that will run WebRun again with the * given arguments (after parsing) */ private WebRun(StringBuffer args) { super(); WebRunArgs=args.toString(); new java.lang.Thread(this,"sub-WebRun").start(); } /** * This string is used when we are trying to start * another WebRun thread with the given argument string */ private volatile String WebRunArgs=null; /** * We store the resources we loaded from the jar files * here. If it already is in here we skip that resource. * Resources are removed if they are classes and they got * loaded. Mainly to keep things clean. Other resources * remain here until they are needed. (A cache :-) */ private java.util.Hashtable Resources=new java.util.Hashtable(); /** * Get a stream with the given url after adding the authorization * and client identification. */ private final java.io.InputStream getStream(String name) throws java.io.IOException { // Keep looping. We will return out of here or // throw an exception out of here. No other real // choice... while (true) { // Build our connection... java.net.URLConnection connect=new java.net.URL(name).openConnection(); // Tell a bit about us to the server connect.setRequestProperty("User-Agent","WebRun Class Loader"); // If we have an authorization key, set it before we try to get it... if (Authorization != null) { // Set the authorization property before we get the stream... connect.setRequestProperty("Authorization","Basic " + Authorization); } // Check if we need to authenticate if (connect.getHeaderField("WWW-Authenticate") == null) { // Get the input stream from the URL connection // and create a complete BufferedReader for it. return(connect.getInputStream()); } // ask for user/password authorization and retry to load... Authorization=getAuthorization(Authorization, connect.getHeaderField("WWW-Authenticate")); } } /** * This actually adds the contents of a jar file to the * class loader for later use. * * @param name This is the name (full URL string) for the jar file. * @exception java.net.MalformedURLException if the URL for the jar file is somehow flawed. * @exception java.io.IOException if there is a problem loading the jar file. */ private final void addJar(String name) throws java.net.MalformedURLException,java.io.IOException { try { // Put up a message (if needed) loadProgress(name); // Get the zip file stream (jars are just zips :-) java.util.zip.ZipInputStream zip=new java.util.zip.ZipInputStream(getStream(name)); java.util.zip.ZipEntry entry; // Now, for each entry in the zip/jar file... while (null != (entry=zip.getNextEntry())) { // We only care if it is not a directory... if (!entry.isDirectory()) { String item=entry.getName(); // We also only care if the entry is not already in the // has table... synchronized (this) { if (!Resources.containsKey(item)) { Resources.put(item,loadBytes(zip)); } } } } zip.close(); } finally { loadProgressDone(name); } } /** * A simple little method that takes an input stream and returns * an array of bytes that were loaded from that stream. * * @param input The input stream * @exception java.io.IOException May be thrown from the input stream */ private final byte[] loadBytes(java.io.InputStream input) throws java.io.IOException { java.io.ByteArrayOutputStream bytes=new java.io.ByteArrayOutputStream(BUF_SIZE); byte[] tmp=new byte[BUF_SIZE]; int read; while (-1 < (read=input.read(tmp,0,tmp.length))) { bytes.write(tmp,0,read); } return(bytes.toByteArray()); } /** * This method will now try to load the given file from our internal * path which happens to be a URL. It will first check to see if the * file is in one of the jars we have loaded before it goes out to the * URL to try to load it. If it is loaded from the jars, it will * remove it from our jar hash table. *
* I hope to make this so it can then handle multiple URLs in * the path. For now, this is all I really care about. */ private final synchronized byte[] loadFile(String name) throws java.net.MalformedURLException,java.io.IOException { // First, check if we have it in the jar holder byte[] result=(byte[]) Resources.remove(name); // If not from something we already have, try the URL... if (result == null) { // Build the URL... if (BaseURL == null) { throw new java.io.FileNotFoundException(name); } // Now read the file and return the bytes... java.io.InputStream input=getStream(BaseURL + name); result=loadBytes(input); input.close(); } return(result); } /** * This method will now try to load the given file from our internal * path which happens to be a URL. It will first check to see if the * file is in one of the jars we have loaded before it goes out to the * URL to try to load it. After it loads the file it will put the * bytes into the resource hash table for caching reasons * (and to undo what may have happened when it was removed during * the actual load) *
* I hope to make this so it can then handle multiple URLs in * the path. For now, one is all I really care about. */ private final synchronized byte[] loadCacheFile(String name) throws java.io.IOException { // First, check if we have it in the jar holder byte[] result=loadFile(name); if (result != null) { Resources.put(name,result); } return(result); } /** * We want to do the class loading in a synchronized block so that * if multiple threads end up calling us it will still be possible * to run. However, we do *not* want to do the resolveClass() in * that block. */ private final synchronized Class getByteCode(String name) throws java.io.IOException { // Check if the class was already loaded Class result=findLoadedClass(name); // Still no class found? Try our class loader... if (result == null) { byte[] buf=loadFile(name.replace('.','/')+".class"); result=defineClass(name,buf,0,buf.length); } return(result); } /** * This is the method we need to override to provide our own class loading * logic. Not as hard as it may seem since we support having the classes * loaded via the system class loader too. * * @param name The fully qualified class name - for example "java.lang.String" * @param resolve Set to true if the class should be fully linked into the system. * @exception java.lang.ClassNotFoundException Is thrown if the class can not * be found or loaded with either the system class loader or * our extension to that loader. */ protected Class loadClass(String name, boolean resolve) throws java.lang.ClassNotFoundException { // Now, in a try block, try to the system's class loader. // Only if this fails do we need to try elsewhere... try { // Try to get the class from the system class loader. return(findSystemClass(name)); } catch (ClassNotFoundException e) { // It was not a system class so try getting it via our // own method (network) } // When we display the message, show [] around the name if // it is going to be resolved and not just loaded. String displayName=resolve ? "["+name+"]" : name; // Get the class byte code... Class result=null; try { loadProgress(displayName); result=getByteCode(name); // If at this point we still don't have a class throw the // ClassNotFound exception. if (result == null) { throw new java.lang.ClassNotFoundException(name); } // At this point we know we have a class (if we don't have // a class, an exception was thrown above so result is not null) // If resolve is true we need to resolve the class... if (resolve) { resolveClass(result); } } catch (java.io.IOException e) { // Check if we have a specific reason String why=e.getMessage(); if (why == null) { // If not, just include the name why=name; } else { // If so, also include the reason why=name + " - " + why; } throw new java.lang.ClassNotFoundException(why); } finally { loadProgressDone(displayName); } // Return the class we found. return(result); } /** * Get an InputStream on a given resource. Will return null if no * resource with this name is found. This can get resources from the * jar files (and will check those first, after the standard system * resources) and from the URL * * @param name the name of the resource, to be used as is. * @return an InputStream on the resource, or null if not found. */ public java.io.InputStream getResourceAsStream(String name) { java.io.InputStream result=getSystemResourceAsStream(name); // If not one of the system resources, check our holdings... if (result == null) { try { loadProgress(name); byte[] data=loadCacheFile(name); if (data != null) { result=new java.io.ByteArrayInputStream(data); } } catch (java.net.MalformedURLException e) { // If the name was such that the URL was // invalid. } catch (java.io.IOException e) { // If the data was not local and was then // loaded over the net and there was some // load error } finally { loadProgressDone(name); } } return(result); } /** * Get the resource as a URL. This can only get resources * that are not in a JAR file since there is no URL that can * point into a JAR file. *
* It first checks the system for the resource. If it is not * there it will then append the URL to the name and return that. *
* This API is really a poor API to use since it depends * on having a URL that can point at the file. This depends on * a number of things including authorization tokens and URL * types being valid *and* that you can even make a URL to such * a location; which is impossible with files that are inside * of a JAR file. This is why getResourceAsStream is so much * better. * * @see #getResourceAsStream(java.lang.String) * @param name The file name for the resource. * @return a URL that points to the resource. This may, however, * not actually work since the resource may be within * a JAR file or behind security in a web server * and the JDK supplied URL handlers do not support * these types of operations. */ public java.net.URL getResource(String name) { // First try the system resource loader... java.net.URL result=getSystemResource(name); // If we don't have the system resource append our // URL to it. if ((result == null) && (BaseURL != null)) { try { result=new java.net.URL(BaseURL + name); } catch (java.net.MalformedURLException e) { result=null; } } return(result); } /** * A flag to enable load status display */ private boolean loadStatusDisplay=false; /** * A flag to enable load status display in an AWT window */ private boolean loadStatusDisplayWindow=false; /** * We prefix messages that are "remove" messages with this. */ private final static String REMOVE_PREFIX="^"; /** * A message that looks like this means close down the window */ private final static String CLOSE_WINDOW_MESSAGE="$$"; /** * The amount of time we wait before hiding the status window * when we are not showing any loading operation. This is in * milliseconds as passed to the Object.wait() method. *
* Currently is set to 3 seconds. */ private static final long HIDE_WINDOW_TIME = 3 * 1000; /** * A simple loading "window" that will provide progress information * if possible. If not, it just becomes a no-op... */ private final void loadProgress(String message) { if (loadStatusDisplay) { if (loadStatusDisplayWindow) { loadStatusMessages.addElement(message); synchronized (this) { notifyAll(); } } else { // If no window, show via STDOUT System.out.println("Loading " + message); } } } /** * Remove an item from the progress window. */ private final void loadProgressDone(String message) { if (loadStatusDisplay && loadStatusDisplayWindow) { loadStatusMessages.addElement(REMOVE_PREFIX + message); synchronized (this) { notifyAll(); } } } /** * This is how we send messages to the thread. It lets us send more * than one at a time in case the thread had gotten backlogged. */ private java.util.Vector loadStatusMessages=new java.util.Vector(); /** * In order to do the AWT based form of the loading display we need * to separate the display from the class loading so that they don't * interact with each other. We try our best to keep the amount of * code that has to run in synchronization blocks to an absolute * minimum and keep as much of the information "local" to this thread. *
* The thread is only created if AWT display of the load progress is * asked for. It handles all of the display updating. *
* We are a window listener specifically so that we can handle the case * of the user requesting to shut down the status window. */ public void run() { try { // This is really, really, sick. If the WebRunArgs stringbuffer // is non-null then this thread is just to start up the next // WebRun process... if (WebRunArgs != null) { main(WebRunArgs); } else if ((exitError != null) && (writePipe != null)) { // This is really sick. We will use this variable to note that // we need to run and push the error out to a pipe... // Now, print the exception stack trace to the pipe... exitError.printStackTrace(writePipe); // Just to take care of the bug in the java.io.BufferedReader // when getting Mac-like line ending characters! (ARG!!!) // It also makes sure that last line has an EOL on it. writePipe.print("\n"); writePipe.print(END_TAG); writePipe.print("\n"); } else { // This is so that the window will react at a reasonably quick pace. // It mainly impacts that non-native thread systems like the Mac, // and it turns out that the Mac is one of the main reasons for this // window in the first place. java.lang.Thread.currentThread().setPriority(java.lang.Thread.MAX_PRIORITY); // Now, build the window and do the work as needed... try { java.awt.Frame loadStatus=new java.awt.Frame("Loading..."); loadStatus.pack(); loadStatus.addWindowListener(this); loadStatus.setLayout(new java.awt.GridLayout(0,1)); loadStatus.setForeground(java.awt.Color.white); loadStatus.setBackground(java.awt.Color.green.darker().darker()); // A hash table so we can keep track of the items being displayed // It is possible (but rare) to nest in the class loader. It is // strange that it does not happen more but the API is such that // it could/should happen so I handle multiple class loadings. java.util.Hashtable loadStatusItems=new java.util.Hashtable(); try { // In case someone asks us to quit by turning this option off... while (loadStatusDisplayWindow) { // Wait for some new work or for our status to change... synchronized (this) { while ((loadStatusDisplayWindow) && (loadStatusMessages.size() == 0)) { if ((loadStatus.isVisible()) && (loadStatus.getComponentCount() == 0)) { // Since we are visible and empty; wait for a limited amount // of time for a message and if it does not show up, hide // the window... wait(HIDE_WINDOW_TIME); // If, after some timeout, we don't see any new reports // hide the window... if (loadStatusMessages.size() == 0) { loadStatus.setVisible(false); } } else { // When invisible or with messages, // we don't care how long we wait // since we don't shut down the window... wait(); } } } // Process all work in one gulp and only then display the final results. while ((loadStatusDisplayWindow) && (loadStatusMessages.size() > 0)) { String message=(String)loadStatusMessages.elementAt(0); loadStatusMessages.removeElementAt(0); if (message.equals(CLOSE_WINDOW_MESSAGE)) { // We turn off all status display flags // and fall our of our loop here which will // then close down the window cleanly... loadStatusDisplay=false; loadStatusDisplayWindow=false; } else if (message.startsWith(REMOVE_PREFIX)) { // This is one we want to remove java.awt.Component item=(java.awt.Component)loadStatusItems.remove(message.substring(REMOVE_PREFIX.length())); if (item != null) { loadStatus.remove(item); } } else { // This is one we will add... // Add our new message to the window... if (loadStatusItems.get(message) == null) { java.awt.Component item=new java.awt.Label(" " + message + " ... ",java.awt.Label.LEFT); loadStatusItems.put(message,item); loadStatus.add(item); } } } // Only play with the window if we still want it... if (loadStatusDisplayWindow) { // When this goes to 0 we just leave the window // alone and let the timeout close it as needed. if (loadStatus.getComponentCount() > 0) { // Now, display the final results. // We will adjust the window size // as needed (if needed) // If we have not become visible yet, show it now... if (!loadStatus.isVisible()) { loadStatus.setVisible(true); } // Check if we want a new size... java.awt.Dimension oldSize=loadStatus.getSize(); java.awt.Dimension tstSize=loadStatus.getPreferredSize(); java.awt.Dimension newSize=new java.awt.Dimension(java.lang.Math.max(oldSize.width,tstSize.width), tstSize.height); // If the size changed, set the new one... if (!newSize.equals(oldSize)) { loadStatus.setSize(newSize); // Now, keep it at the bottom right of the screen... //java.awt.Dimension screen=loadStatus.getToolkit().getScreenSize(); //java.awt.Dimension window=loadStatus.getSize(); //loadStatus.setLocation((screen.width - window.width),(screen.height - window.height)); } // Fix up the layout and make sure it is displayed... loadStatus.validate(); loadStatus.repaint(); } } } } catch (java.lang.Throwable e) { // If we get any errors thrown we // exit out of this. } // Clean up the window... loadStatus.setVisible(false); loadStatus.dispose(); } catch (java.lang.Throwable e) { // If we get any errors thrown here // we can't use the AWT to show progress } // Set this flag to false just so that // if we are trying to display that it // will now be to STDOUT. loadStatusDisplayWindow=false; // And make sure we clean out any junk... loadStatusMessages.removeAllElements(); } } catch (java.lang.Throwable e) { // Any errors in this thread are to be ignored // as there is nobody on the other end that // can listen to them anyway. } } // For the window listener support - we need this to let // the user close down status window if they happen to have it // up. We just tell the status window handler to close down. // We also use this to get the username text field active as // the window opens up for the password requester. public void windowActivated(java.awt.event.WindowEvent event) { // Try to get the text field to have focus... if (TextField_Username != null) { TextField_Username.requestFocus(); } } public void windowDeactivated(java.awt.event.WindowEvent event) { } public void windowOpened(java.awt.event.WindowEvent event) { // Try to get the text field to have focus... if (TextField_Username != null) { event.getComponent().requestFocus(); TextField_Username.requestFocus(); } } public void windowClosing(java.awt.event.WindowEvent event) { if (event.getSource() == errorSimpleWindow) { // If this was the user trying to close the // error display window we will happily // close it down. errorSimpleWindow.setVisible(false); errorSimpleWindow.dispose(); } else if (event.getSource() == errorDisplayWindow) { // Just let it go away - we will dispose of it // later... errorSimpleWindow.show(); errorDisplayWindow.setVisible(false); } else if (loadStatusDisplayWindow) { // Send the close-down message when the user clicks // on the window close button (only if the window // is actually enabled...) loadStatusMessages.addElement(CLOSE_WINDOW_MESSAGE); synchronized (this) { notifyAll(); } } } public void windowClosed(java.awt.event.WindowEvent event) { // If this was the error display window closing down // we want to force an exit. if (event.getSource() == errorSimpleWindow) { System.exit(5); } } public void windowIconified(java.awt.event.WindowEvent event) { } public void windowDeiconified(java.awt.event.WindowEvent event) { } // We have coded this requester using these "global" variables such // that the whole WebRun class loader can be a single class file. // This prevents us from using inner classes for such things as // component listeners. private java.awt.TextField TextField_Username=null; private java.awt.TextField TextField_Password=null; private java.awt.Button Button_ok=null; private java.awt.Button Button_cancel=null; /** * The action listener for our password entry window. */ public void actionPerformed(java.awt.event.ActionEvent e) { Object src=e.getSource(); if (src == TextField_Username) { TextField_Password.requestFocus(); } else if (src == (Object) TextField_Password) { // Same as pressing OK... Button_ok=null; } else if (src == (Object) Button_ok) { Button_ok=null; } else if (src == (Object) Button_cancel) { Button_cancel=null; } else if (src == errorDetails) { // If they ask for details, show the window... errorDisplayWindow.show(); errorSimpleWindow.setVisible(false); } else if (src == errorClose) { // If they ask to close the error window, do so... errorSimpleWindow.setVisible(false); errorSimpleWindow.dispose(); } // Signal back to me that this happened... synchronized (this) { notifyAll(); } } /** * This method will display a requester asking for user name * and password and then will return the authorization token * needed. This is an instance method since we will need to * have a simple listener set up to know about the button * press. *
This method should never be called directly * except by the static getAuthorization method.
* * @param oldAuth This is the old authorization string. It is * mainly included as a parameter so that the requester * can display the fact that the previous attempt failed. * If this is null then it assumes no previous authorization. * @param httpInfo This is the string that the HTTP server provides * when an request fails due to athentication errors. If * this is null it is assumed that no such information is * available. */ private final String getAuth(String oldAuth,String httpInfo) { // Assume no answer... String result=null; // Build the window and the buttons... java.awt.Frame window=new java.awt.Frame("Enter Username and Password"); try { window.setBackground(java.awt.Color.lightGray); window.setResizable(false); window.addWindowListener(this); // If we have a relm information string, add it to the requester. if (httpInfo != null) { int p1=httpInfo.indexOf('"'); int p2=httpInfo.lastIndexOf('"'); // Check that there is actually something in the string... if ((p2-p1) > 1) { window.add(new java.awt.Label(httpInfo.substring(p1+1,p2) + " requires password:", java.awt.Label.LEFT), java.awt.BorderLayout.NORTH); } } // If we had a previous authorization string, let the user know // that it failed. if (oldAuth != null) { window.add(new java.awt.Label("Authorization failed - try again", java.awt.Label.CENTER), java.awt.BorderLayout.SOUTH); } // Build the center panel... java.awt.Panel center=new java.awt.Panel(new java.awt.GridLayout(3,2,2,2)); window.add(center,java.awt.BorderLayout.CENTER); center.add(new java.awt.Label("Username:",java.awt.Label.RIGHT)); TextField_Username=new java.awt.TextField(16); TextField_Username.addActionListener(this); center.add(TextField_Username); center.add(new java.awt.Label("Password:",java.awt.Label.RIGHT)); TextField_Password=new java.awt.TextField(16); TextField_Password.setEchoChar('*'); TextField_Password.addActionListener(this); center.add(TextField_Password); Button_ok=new java.awt.Button("OK"); Button_ok.addActionListener(this); center.add(Button_ok); Button_cancel=new java.awt.Button("Cancel"); Button_cancel.addActionListener(this); center.add(Button_cancel); // Layout/pack the contents... window.pack(); // Center it on screen... java.awt.Dimension screen=window.getToolkit().getScreenSize(); window.setLocation((screen.width - window.getSize().width)/2, (screen.height - window.getSize().height)/2); // Show the window... window.setVisible(true); // Try to get the text field to have focus... TextField_Username.requestFocus(); // Wait for some results from our action listener methods... try { synchronized(this) { while ((Button_ok != null) && (Button_cancel != null)) { wait(); } } } catch (java.lang.InterruptedException e) { // An interruption is the same as a cancel? } // Hide the window... window.setVisible(false); // If all is ok, encode the result and clean up... if (Button_ok == null) { result=encode(TextField_Username.getText(),TextField_Password.getText()); } else { // I assume a cancel means stop trying to run... System.exit(5); } } finally { // Some cleanup we always want to do... TextField_Username=null; TextField_Password=null; Button_ok=null; Button_cancel=null; // Get rid of the window... window.dispose(); } // Return the encoded authorization token (or null) return(result); } // We use this to write to the pipe from the outside... private volatile java.io.PrintWriter writePipe=null; private volatile java.lang.Throwable exitError=null; private static final String END_TAG=WebRun.class.getName(); /** * This internal method will put up a requester with the text of * the exception stack trace in it. This is done with some pipes * and a bit of fun. ** Note that the initial window that is shown is actually a * "geek text" prophylactic window. It just says an error happened * and gives the option to show details. This window will * time out in a given period (currently 60 seconds) but it * can be closed earlier. The details window will not time out. * * @param e The throwable object that contains the error. */ private final void showErr(java.lang.Throwable e) throws java.io.IOException { // Now try to get the pipes connected... java.io.PipedReader pipe = new java.io.PipedReader(); java.io.BufferedReader readPipe = new java.io.BufferedReader(pipe,16000); // 16k stack trace limit... writePipe = new java.io.PrintWriter(new java.io.PipedWriter(pipe)); exitError = e; // Start my thread new java.lang.Thread(this).start(); // Now, lets read the results until we get to the end tag... StringBuffer textBuffer=new StringBuffer("An error was encountered during execution:\n"); textBuffer.append(e.toString()).append("\n\n"); String tmp="Details of the error follow:\n"; while (!tmp.equals(END_TAG)) { textBuffer.append(tmp).append("\n"); tmp=readPipe.readLine(); } // Now that we have the text in a big buffer (with LFs) // we can call the method that will do the actual display showErrString(e.toString(),textBuffer.toString()); } // We use this as our window reference so that the listener // can know what to do. private java.awt.Frame errorDisplayWindow=null; private java.awt.Frame errorSimpleWindow=null; private java.awt.Button errorDetails=null; private java.awt.Button errorClose=null; /** * This internal method will put up a requester with the text * given as the parameter. * * @param error The text (which can be multi-line) */ private final void showErrString(String title,String error) { // Build the simple error window first. If the user asks for details, // then build the fancy window... errorDetails=new java.awt.Button("Show Details"); errorDetails.addActionListener(this); errorClose=new java.awt.Button("Close"); errorClose.addActionListener(this); java.awt.Panel textPanel=new java.awt.Panel(new java.awt.GridLayout(0,1)); textPanel.add(new java.awt.Label("")); textPanel.add(new java.awt.Label(" An error was detected during execution.")); textPanel.add(new java.awt.Label(" You may wish to contact technical support.")); textPanel.add(new java.awt.Label("")); errorSimpleWindow=new java.awt.Frame("Error during execution"); errorSimpleWindow.setBackground(java.awt.Color.lightGray); errorSimpleWindow.add(textPanel,java.awt.BorderLayout.NORTH); errorSimpleWindow.add(errorClose,java.awt.BorderLayout.WEST); errorSimpleWindow.add(errorDetails,java.awt.BorderLayout.EAST); errorSimpleWindow.addWindowListener(this); // Build the details window and the buttons... java.awt.TextArea text=new java.awt.TextArea(error,25,80); text.setEditable(false); text.setFont(new java.awt.Font("Monospaced",java.awt.Font.PLAIN,12)); text.setBackground(java.awt.Color.lightGray); text.setForeground(java.awt.Color.black); errorDisplayWindow=new java.awt.Frame(title); errorDisplayWindow.setBackground(java.awt.Color.lightGray); errorDisplayWindow.add(text); errorDisplayWindow.addWindowListener(this); errorDisplayWindow.pack(); // We don't show the details window - it will get shown // when the button is pushed... // Now show the simple window... errorSimpleWindow.pack(); errorSimpleWindow.show(); // This should wait for a long time... try { // Leave the error up for no more than this long // but if the details are visible, don't time out... do { java.lang.Thread.sleep(ERROR_TIMEOUT); } while (errorDisplayWindow.isVisible()); } catch (java.lang.InterruptedException e) { // This will most likely never get thrown since // none of this code will try to interrupt this // call to wait()... } finally { errorSimpleWindow.setVisible(false); errorSimpleWindow.dispose(); } } /** * For base64 encoding, this is the table of characters... */ private static final char[] alphabet = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55 '4', '5', '6', '7', '8', '9', '+', '/' // 56 to 63 }; /** * A Simple base64 encoding of a string of characters. * See RFC 1421. *
* This is used to make the authorization string for web
* pages that need passwords.
*
* @param user The user name
* @param password The password
*/
private static String encode(String user,String password)
{
byte[] octetString=(user + ":" + password).getBytes();
int bits24;
int bits6;
char[] out = new char[((octetString.length-1)/3+1)*4];
int outIndex = 0;
int i = 0;
while ((i + 3) <= octetString.length)
{
// store the octets
bits24 = (octetString[i++] & 0xFF) << 16;
bits24 |= (octetString[i++] & 0xFF) << 8;
bits24 |= (octetString[i++] & 0xFF) << 0;
bits6 = (bits24 & 0x00FC0000) >> 18;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x0003F000) >> 12;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x00000FC0) >> 6;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x0000003F);
out[outIndex++] = alphabet[bits6];
}
if (octetString.length - i == 2)
{
// store the octets
bits24 = (octetString[i] & 0xFF) << 16;
bits24 |= (octetString[i + 1] & 0xFF) << 8;
bits6 = (bits24 & 0x00FC0000) >> 18;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x0003F000) >> 12;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x00000FC0) >> 6;
out[outIndex++] = alphabet[bits6];
// padding
out[outIndex++] = '=';
}
else if (octetString.length - i == 1)
{
// store the octets
bits24 = (octetString[i] & 0xFF) << 16;
bits6 =(bits24 & 0x00FC0000) >> 18;
out[outIndex++] = alphabet[bits6];
bits6 = (bits24 & 0x0003F000) >> 12;
out[outIndex++] = alphabet[bits6];
// padding
out[outIndex++] = '=';
out[outIndex++] = '=';
}
return(new String(out));
}
/**
* Send the usage information to the provided stream.
*
* @param out The PrintStream that will be used to display the usage.
*/
private static void baseusage(java.io.PrintStream out)
{
out.println("");
out.println("Usage: java " + WebRun.class.getName() + "
* This method also tracks the athorization on a per-httpInfo
* basis and will return the pre-existing authorization if that
* is not the input authorization.
*
* @param oldAuth This is the old authorization string. It is
* mainly included as a parameter so that the requester
* can display the fact that the previous attempt failed.
* If this is null then it assumes no previous authorization.
* @param httpInfo This is the string that the HTTP server provides
* when an request fails due to athentication errors. If
* this is null it is assumed that no such information is
* available.
*/
private static final String getAuthorization(String oldAuth,String httpInfo)
{
// The authorization we will return...
String result=null;
// Check if we are to try to find a previous authorization
if (httpInfo != null)
{
// Check our hash table for a previous authorization for
// this httpInfo...
String guess=(String) AuthorizationList.get(httpInfo);
// If we have no previous information, try the default
if (guess == null)
{
guess=DefaultAuthorization;
}
// Now, check if the previous happens to match what
// we already had tried. If it does not, return this
// one.
if ((guess != null) && (oldAuth != null))
{
if (guess.equals(oldAuth))
{
// They are the best-guess value
// is the same as the old one so
// we thus know that this best-guess
// is no good and will try again.
guess=null;
}
}
// If we still have a valid guess set our result
// to that...
result=guess;
}
// Now, if we have no result, we need to try to get
// one from the requester...
if (result == null)
{
result=new WebRun().getAuth(oldAuth,httpInfo);
}
// If we have a result, store it for future guesswork.
if (result != null)
{
// If we have an httpInfo value we store it
// in the hash table otherwise it is stored
// in the default authorization field.
if (httpInfo != null)
{
AuthorizationList.put(httpInfo,result);
}
else
{
DefaultAuthorization=result;
}
}
return(result);
}
/**
* Run the given class's main() method with the arguments given
* after loading it from the class loader.
*
* @param loader The class loader to use.
* @param className The fully qualified name of the class to run.
* @param args The arguments that will be passed to main() in the class.
* @exception java.lang.Throwable Anything that gets thrown by this will
* just be passed along. This catches nothing.
*/
public static void runClass(java.lang.ClassLoader loader,String className,String[] args) throws java.lang.Throwable
{
// The argument type array we need to find the right method
// from the class we will load.
Class[] argTypes={(new String[0]).getClass()};
// The argument array we need for the reflection work...
Object[] arg={args};
// Using the loader provided (we don't check it)
// load the class name given and find the main(String[])
// method and invoke it with the arguments given.
loader.loadClass(className).getMethod("main",argTypes).invoke(null,arg);
}
/**
* Get a reader with the given url after adding the authorization
* and client identification.
*/
private static java.io.InputStream getReaderStream(String name) throws java.io.IOException
{
// Start out with the default authorization...
String Authorization=DefaultAuthorization;
// Keep looping. We will return out of here or
// throw an exception out of here. No other real
// choice...
while (true)
{
// Build our connection...
java.net.URLConnection connect=new java.net.URL(name).openConnection();
// Tell a bit about us to the server
connect.setRequestProperty("User-Agent","WebRun Class Loader");
// If we have an authorization key, set it before we try to get it...
if (Authorization != null)
{
// Set the authorization property before we get the stream...
connect.setRequestProperty("Authorization","Basic " + Authorization);
}
// Check if we need to authenticate
if (connect.getHeaderField("WWW-Authenticate") == null)
{
// Get the input stream from the URL connection
// and create a complete BufferedReader for it.
return(connect.getInputStream());
}
// ask for user/password authorization and retry to load...
Authorization=getAuthorization(Authorization,
connect.getHeaderField("WWW-Authenticate"));
}
}
/**
* Get a reader with the given url after adding the authorization
* and client identification.
*/
private static final java.io.BufferedReader getReader(String name) throws java.io.IOException
{
return(new java.io.BufferedReader(new java.io.InputStreamReader(getReaderStream(name))));
}
/**
* Parse out a token and put it into the string buffer provided
* starting with the character provided.
* This returns the next non-whitespace character read after the token.
*/
private static int parseToken(int c,java.io.Reader in,StringBuffer token) throws java.io.IOException
{
c=skipSpaces(c,in);
while (((c >= 'a') && (c <= 'z')) ||
((c >= 'A') && (c <= 'Z')) ||
((c >= '0') && (c <= '9')) ||
(c == '_') ||
(c == '-'))
{
token.append((char)c);
c=in.read();
}
return(skipSpaces(c,in));
}
/**
* Parse out a token and put it into the string buffer provided.
* This returns the next non-whitespace character read after the token.
*/
private static int parseToken(java.io.Reader in,StringBuffer token) throws java.io.IOException
{
return(parseToken(in.read(),in,token));
}
/**
* This returns the character if it is not a white space.
* If it is a whitespace, it skips to the next non-whitespace
* character.
*/
private static int skipSpaces(int c,java.io.Reader in) throws java.io.IOException
{
while ((c >= 0) && java.lang.Character.isWhitespace((char)c))
{
c = in.read();
}
return(c);
}
/**
* Returns the next non-whitespace character.
*/
private static int skipSpaces(java.io.Reader in) throws java.io.IOException
{
return(skipSpaces(in.read(),in));
}
/**
* This is the name of the content in meta tags that
* defines the arguments for WebRun to us. See the
* parseWebRun() method for details as to its use.
*/
private static final String WEBRUN_NAME="WebRun";
/**
* Parse an HTML document looking for a WebRun definition.
* Each WebRun definition found will be passed to WebRun
* as its own thread for processing. Thus a page may
* define multiple applications that should run.
*
* WebRun definitions are stored in the <META> tags in
* the HTML. A WebRun definition would look much like
* the following:
*
* This was done such that the same URL/HTML document can
* include the directions on how to use WebRun *and* the
* actual meta tags needed to run the application.
*
* @param url The string representation of the URL. This
* would contain something like "http://www.foo.com/mydoc.html"
* Any valid URL is supported but only HTTP connections
* currently support authentication.
*/
private static void parseWebRun(String url) throws java.io.IOException
{
// Create a reader for the named URL we are given.
java.io.BufferedReader reader=getReader(url);
// We now scan the file looking for tags...
for (int c=reader.read(); c >= 0; c=reader.read())
{
// Looks like we are starting a new tag...
if (c == '<')
{
// Get the token...
// Note that they can only have certain
//
StringBuffer token=new StringBuffer();
c=parseToken(reader,token);
// ...check if this is a 'META' tag
if (token.toString().equalsIgnoreCase("META"))
{
// Ahh, a meta tag and we are now
// ready to start parsing it. This
// is where life gets interesting...
// We are only interested in the
// content argument when the name
// argument is "WebRun" Thus we will
// only worry about the content and name
// options...
StringBuffer content=null;
String name=null;
while ((c>=0) && (c != '>'))
{
StringBuffer id=new StringBuffer();
c=parseToken(c,reader,id);
// Check that we got a token...
if (id.length() > 0)
{
if (c == '=')
{
StringBuffer value=new StringBuffer();
c=skipSpaces(reader);
// If the first character is a quote then
// we have a special parsing rule...
if ((c == '"') || (c == '\''))
{
int Quote=c;
// Start to build the value...
c=reader.read();
while ((c >= 0) && (c != Quote))
{
if (java.lang.Character.isWhitespace((char)c))
{
value.append(" ");
}
else
{
value.append((char)c);
}
c=reader.read();
}
// When we get to the ending quote, read past it...
if (c == Quote)
{
c=reader.read();
}
}
else
{
// The value did not start with
// a quote so now we have to deal
// with the simplistic cases of
// what can be in the parameter.
// This basically comes down to
// the same rules as a token...
c=parseToken(c,reader,value);
}
// Ok, now, lets see if it is what we want...
if (id.toString().equalsIgnoreCase("NAME"))
{
name=value.toString();
}
if (id.toString().equalsIgnoreCase("CONTENT"))
{
content=value;
}
}
}
else if (c >=0)
{
// We failed to parse a token. Lets make sure
// it looks like wea re at the end of the road.
c='>';
}
}
if ((name != null) && (content != null))
{
if (name.equalsIgnoreCase(WEBRUN_NAME))
{
// We want to run WebRun with these parameters...
new WebRun(content);
}
}
}
else
{
// If we are not interested, lets
// head on out. We don't actually
// try to parse to the end of the tag
// since it does not buy us anything
// and it will make it that much easier
// to have a broken document.
}
}
}
}
/**
* Run WebRun with the given string is an un-parsed argument list
* to WebRun. We will break it up and call the real main() method.
*/
private static void main(String arg) throws java.lang.Throwable
{
// We build our arguments into here...
java.util.Vector arguments=new java.util.Vector();
// Now parse the arguments into their own little strings...
int p=0;
while (p < arg.length())
{
char c=arg.charAt(p);
if (!java.lang.Character.isWhitespace(c))
{
// Start of another argument.
int start=p;
int end=arg.length();
// Check if we have a quote...
if ((c == '"') || (c == '\''))
{
// Ahh, a quoted argument, lets
// go an deal with that...
char Quote=c;
p++;
start=p;
while (p < arg.length())
{
c=arg.charAt(p);
if (c == Quote)
{
end=p;
// Skip past the ending quote...
p++;
break;
}
// Keep looking until we get to the end of the argument...
p++;
}
}
else
{
// Ahh, a simple argument, lets deal
// with that...
while (p < arg.length())
{
c=arg.charAt(p);
if (java.lang.Character.isWhitespace(c))
{
end=p;
break;
}
// Keep looking until we get to the end of the argument...
p++;
}
}
arguments.addElement(arg.substring(start,end));
}
else
{
// Skip the white space...
p++;
}
}
// Now that we have our arguments, make our array and run main()
String[] args=new String[arguments.size()];
arguments.copyInto(args);
main(args);
}
/**
* The main entry point for the remote application launcher.
*
* @exception java.lang.Throwable This will throw anything that
* could be thrown by the main() method of the class
* that got loaded along with possible exceptions
* for not finding the class or other class loading
* errors.
*/
public static void main(String[] args) throws java.lang.Throwable
{
String baseURL=null;
// Verbose loading
boolean Verbose=false;
boolean VerboseWindow=false;
// By default we want errors to be brought up in a
// window for the user to see.
boolean noErrorRequester=false;
// This is where we will store the class name we are looking
// for. We can only get one and it will be the first
// argument that is not specific to this class.
String className=null;
// The authorization string, if needed...
String authorization=null;
// The authorization requester flag - set to true to prevent the requester
boolean noAuthorizationRequester=false;
// A vector containing the list of jar files to use
java.util.Vector jarList=new java.util.Vector();
// We will put all of the unknown arguments into here
// so that we can then make a new argument array for
// loaded class array.
java.util.Vector newArgs=new java.util.Vector();
// If there is only one argument and it does not start with
// the "-" character then we assume it is a URL which points
// at the file that contains the WebRun arguments.
if ((args.length == 1) && (!args[0].startsWith("-")))
{
parseWebRun(args[0]);
}
else
{
try
{
String user=null;
String password=null;
for (int i=0;i < args.length;i++)
{
// If we are done with arguments the rest just get
// put into the argument list for the class
if (className != null)
{
newArgs.addElement(args[i]);
}
else if ((args[i].length() == 2) && (args[i].charAt(0) == '-'))
{
// Check the argument character...
switch (args[i].charAt(1))
{
case 'H': // help
fullusage(System.out);
System.exit(0);
break;
case 'h': // help
usage(System.out);
System.exit(0);
break;
case 'u': // user name
i++;
user=args[i];
break;
case 'p': // password
i++;
password=args[i];
break;
case 'R': // Never request
noAuthorizationRequester=true;
break;
case 'E': // No error requester
noErrorRequester=true;
break;
case 'r': // Reqester for authorization
authorization=getAuthorization(null,null);
break;
case 'a': // authorization token
i++;
authorization=args[i];
break;
case 'g': // generate and display token from user/password
authorization=encode(args[i+1],args[i+2]);
System.out.println("Authorization token is:");
System.out.println(authorization);
System.exit(0);
case 'G': // generate and display token from requester
authorization=getAuthorization(null,null);
System.out.println("Authorization token is:");
System.out.println(authorization);
System.exit(0);
case 'l': // class file location url
if (baseURL != null)
{
throw new java.lang.Error("Only one URL option is permitted.");
}
i++;
baseURL=args[i];
// Make sure the base URL ends with a "/"
if (!baseURL.endsWith("/"))
{
baseURL=baseURL + "/";
}
break;
case 'j': // another jar file...
i++;
jarList.addElement(args[i]);
break;
case 'w':
VerboseWindow=true;
Verbose=true;
break;
case 'v':
Verbose=true;
break;
default:
// An unknown argument before the class name
// is an error...
throw new java.lang.Error("Unknown argument: " + args[i]);
}
}
else
{
// Once we get the class name, we assume that it is
// now just arguments for the class.
className=args[i];
}
}
if ((authorization == null) && (user != null) && (password != null))
{
authorization=encode(user,password);
}
if (className == null)
{
throw new java.lang.NoClassDefFoundError("Missing name of class to run");
}
}
catch (java.lang.NoClassDefFoundError e)
{
// Just in case the user was confused...
System.err.println(e.toString());
usage(System.err);
System.exit(1);
}
catch (java.lang.Throwable e)
{
// Just in case the user was confused on the options...
System.err.println(e.toString());
usage(System.err);
// If we have not been asked to not uses the error requester
// then put this error in the requester.
if (!noErrorRequester)
{
// Note that calling this will cause an exit when it is through.
new WebRun().showErr(e);
}
// Make sure an exit happens...
System.exit(1);
}
// Ok, so we may have everything. Lets try to run...
try
{
// Make our class loader...
// Show the progress "windows" if verbose has been set...
WebRun loader=new WebRun(baseURL,authorization,noAuthorizationRequester,Verbose,VerboseWindow);
// Install this loader in the class loader chain to avoid class cast
// exceptions that can occur if a class appears in both an explicitly
// downloaded .jar and in a remote codebase referenced by the .jar
// mnoble@cfa.harvard.edu (11/1/2000)
//Thread.currentThread().setContextClassLoader(loader);
// The above does not work in JDK 1.1 systems so we will need to
// do things a bit differently... (How, I am not sure yet.)
// Now, start processing all of the jar files
for (int i=0; i<META NAME="WebRun" CONTENT="webrun_args">