/* $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; /** *

$Id: WebRun.java,v 2.7 2002/01/19 16:13:21 mks Exp $

*
*
* Copyright 1998-2001 Michael Sinz.  All rights reserved.
*

* This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version.  The * source code must always be available via any distribution * mechanism by which the binary is made available. *
* This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. *
* You should have received a copy of the GNU General Public License * along with this program; if not, write to: *
* Free Software Foundation, Inc. * 59 Temple Place * Suite 330 * Boston, MA 02111-1307 * USA. *

* *

$Id: WebRun.java,v 2.7 2002/01/19 16:13:21 mks Exp $

* A simple Java Application Starter that can load jars and class files * from URLs, including password protected URLs. * WebRun offers the benefits of Java without the overhead or * bugs of web browsers. *

* 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:
parameterdescription
-hHelp - displays the usage information
-RNever put up a requester to get the user name and password
-EDo not put up a requester with the error codes
-l URLA URL to the directory where the class tree starts
-j JarURLA URL to a Jar file (you can use -j more than once
-vVerbose loading (stdout)
-wVerbose loading (window)
Options only shown with '-H'
-rPut up a requester to get the user name and password
-u userUser name for authorization
-p passwordPassword for authorization
-a authorizationThe encoded authorization string
-g user passwordGenerate an encoded authorization string
-GPut 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.

* *

Using WebRun with Apple Macintosh computers

*

* 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. *

Using WebRun with Microsoft Windows 95/98/NT/2000

*

* 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. *

Using WebRun with Unix Systems such as Solaris, Linux, FreeBSD, AIX, etc.

*

* 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() + " "); out.println("or java " + WebRun.class.getName() + " [run_options] [options]"); out.println(""); out.println("Where [run_options] can include:"); out.println("\t-h - Show this usage output"); out.println("\t-R - No requester during loading"); out.println("\t-E - No error requesters"); out.println("\t-l - Class file location"); out.println("\t-j - URL to a jar file"); out.println("\t-j - URL to another jar file"); out.println("\t-v - Verbose loading (stdout)"); out.println("\t-w - Verbose loading (window)"); } /** * Send the usage information to the provided stream. * * @param out The PrintStream that will be used to display the usage. */ private static void usage(java.io.PrintStream out) { /* * Silly stuff - to get my class name. This is * so that we can have the usage line be correct * no matter what package we are compiled into. */ baseusage(out); out.println("and"); out.println("\t [options] - The class to run and its options"); out.println(""); } /** * Send the usage information to the provided stream. * * @param out The PrintStream that will be used to display the usage. */ private static void fullusage(java.io.PrintStream out) { /* * Silly stuff - to get my class name. This is * so that we can have the usage line be correct * no matter what package we are compiled into. */ baseusage(out); out.println("\t-u - Web server user"); out.println("\t-p - Web server password"); out.println("\t-a - Encoded user/password"); out.println("\t-r - Request user/password window"); out.println("\t-g - Generate encoded user/password"); out.println("\t-G - Generate encoded user/password"); out.println("and"); out.println("\t [options] - The class to run and its options"); out.println(""); } /** * A hash table we use to track our authorization values. */ private static java.util.Hashtable AuthorizationList=new java.util.Hashtable(); private static String DefaultAuthorization=null; /** * 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 static method is the only one that should be * called.  It will create the objects needed in order to * produce the requester. *

* 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: *

<META NAME="WebRun" CONTENT="webrun_args">

* 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