/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 New Zealand Digital Library Project * * 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. * * 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 the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ package org.greenstone.gatherer; /************************************************************************************** * Written: ??/??/02 * Revised: ??/??/02 - Commented * ??/??/03 - Added support for local library server * 20/07/03 - Rewrote argument parsing so that spaces no longer cause GLI to die. Also added time out when starting local library. **************************************************************************************/ import com.l2fprod.gui.*; import com.l2fprod.gui.plaf.skin.*; import com.l2fprod.util.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.lang.*; import java.net.*; import java.util.*; import javax.swing.*; import javax.swing.plaf.*; import javax.swing.text.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.GAuthenticator; import org.greenstone.gatherer.cdm.CommandTokenizer; import org.greenstone.gatherer.collection.CollectionManager; import org.greenstone.gatherer.file.FileManager; import org.greenstone.gatherer.file.FileAssociationManager; import org.greenstone.gatherer.gui.Coloring; import org.greenstone.gatherer.gui.GUIManager; import org.greenstone.gatherer.gui.Splash; import org.greenstone.gatherer.gui.URLField; import org.greenstone.gatherer.gui.WarningDialog; import org.greenstone.gatherer.msm.MetadataSetManager; import org.greenstone.gatherer.util.ArrayTools; import org.greenstone.gatherer.util.GSDLSiteConfig; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.Utility; import sun.misc.*; /** Containing the main() method for the Gatherer, this class is the starting point for the rest of the application. It first parses the command line arguments, preparing to update the configuration as required. Next it loads several important support classes such as the Configuration and Dictionary. Finally it creates the other important managers and sends them on their way. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3 */ // How to catch All Exceptions including those from the AWT Event thread. // Step 1: register a handler class in your main() // // System.setProperty("sun.awt.exception.handler", "YourHandler"); // // Step 2: implement your handler class. // // public class YourHandler { // public void handle(Throwable thrown) { // eat the exception without dumping to the console. // } // } public class Gatherer { static public Hashtable authentications = new Hashtable(); static final private String SKIN_DEFINITION_FILE = "lib/greenaqua/greenaqua.xml"; /** Has the exit flag been set? true if so, false otherwise. */ public boolean exit = false; /** The size of the Gatherer window. */ public Dimension frame_size = null; /** A temporary shared memory area to store HIndexes to speed up metadata.xml writing. */ public Hashtable known_indexes = null; /** Legacy copy of the debug_ps. */ public PrintStream debug_ps; /** All of the external applications that must exit before we close the Gatherer. */ public Vector apps = new Vector(); /** Messages that have been issued before we have anyway to show them, ie prior to Log initialization. */ public Vector waiting_messages = new Vector(); /** The manager in charge of remembering what file extension gets opened with what program. */ static public FileAssociationManager assoc_man; /** A public reference to the CollectionManager. */ static public CollectionManager c_man; /** A public reference to the Gatherer's configuration. */ static public Configuration config; /** A public reference to the Dictionary. */ static public Dictionary dictionary; /** A public reference to the FileManager. */ static public FileManager f_man; /** A public reference to the GUIManager. */ static public GUIManager g_man; /** A static reference to ourselves. */ static public Gatherer self; /** The debug print stream. */ static public PrintStream debug; /** The name of the necessary environment variable to check for in the programs environment. */ static public String KEY = "GSDLPATH"; /** Extra environment information which must be set before shell processes will run properly. Should always be null if the startup script/program has done its job properly. */ static public String extra_env[] = null; private GSDLSiteConfig gsdlsite_cfg = null; private ExternalApplication server = null; /** The name of the Gatherers configuration file. */ static private String CONFIG_FILE_NAME = "gatherer.cfg"; /** Magic to allow Enter to fire the default button. */ static { KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); Keymap map = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); map.removeKeyStrokeBinding(enter); } /** Constructor. Make the three main modules, c_man, f_man and g_man, and any other necessary classes such as Dictionary. * @param size The desired size of the Gatherer window as a Dimension. * @param gsdl_path The path to the gsdl directory, gathered from the startup arguments, and presented as a String. * @param exec_path The path to the library executable, gathered from the startup arguments, and presented as a String. * @param debug_enabled true to print verbose debug messages to "debug.txt", false otherwise. * @param perl_path The path to the PERL compiler as a String. Necessary for windows platform versions. * @param splash A reference to the splash screen. * @param no_load true to prevent the previously opened collection from reopening. * @see java.io.FileOutputStream * @see java.io.PrintStream * @see java.lang.Exception * @see java.lang.StringBuffer * @see java.util.Calendar * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Dictionary * @see org.greenstone.gatherer.Gatherer.CTRLCHandler * @see org.greenstone.gatherer.GAuthenticator * @see org.greenstone.gatherer.collection.CollectionManager * @see org.greenstone.gatherer.file.FileManager * @see org.greenstone.gatherer.gui.GUIManager * @see org.greenstone.gatherer.gui.Splash */ public Gatherer() { this.self = this; } public void run(Dimension size, String gsdl_path, String exec_path, boolean debug_enabled, String perl_path, boolean no_load, Splash splash, String open_collection) { // This will hopefully catch ctrl-c and terminate, and exit gracefully. However it is platform specific, and may not be supported by some JVMs. /** It does, but I get bloody sick of it working when the Gatherer hangs. CTRLCHandler handler = new CTRLCHandler(); Signal.handle(new Signal("INT"), handler); Signal.handle(new Signal("TERM"), handler); handler = null; */ // Create the debug stream only if required. if(debug_enabled) { try { Calendar now = Calendar.getInstance(); StringBuffer name = new StringBuffer("debug"); name.append(now.get(Calendar.DATE)); name.append("-"); name.append(now.get(Calendar.MONTH)); name.append("-"); name.append(now.get(Calendar.YEAR)); name.append(".txt"); this.debug = new PrintStream(new FileOutputStream(name.toString())); Properties props = System.getProperties(); props.list(debug); // Legacy debug_ps = debug; } catch(Exception error) { ///ystem.err.println("Error in Gatherer.init(): " + error); error.printStackTrace(); System.exit(1); } } try { // Load Config loadConfig(gsdl_path, exec_path, perl_path); // Read Dictionary dictionary = new Dictionary(config.getLocale("general.locale", true), config.getFont("general.font", true)); if (gsdl_path == null) { missingGSDL(dictionary); } // If we were given a server run it if neccessary. if (config.exec_file != null) { startServerEXE(); } // Having loaded the configuration (necessary to determine if certain warnings have been disabled) and dictionary, we now check if the necessary path variables have been provided. if (config.exec_file == null && config.exec_address == null) { if (config.exec_file == null) { Gatherer.println("config.exec_file is null"); } if (config.exec_address == null) { Gatherer.println("config.exec_address is null"); } missingEXEC(dictionary); } // Perl path is a little different as it is perfectly ok to start the Gatherer without providing a perl path boolean found_perl = false; if (config.perl_path != null) { // See if the file pointed to actually exists File perl_file = new File(config.perl_path); found_perl = perl_file.exists(); perl_file = null; } if (config.perl_path == null || !found_perl) { // Run test to see if we can run perl as is. PerlTest perl_test = new PerlTest(); if (perl_test.found()) { // If so replace the perl path with the system default (or null for unix). config.perl_path = perl_test.toString(); found_perl = true; } } if (!found_perl) { // Time for an error message. missingPERL(dictionary); } // Size and place the frame on the screen Rectangle bounds = config.getBounds("general.bounds", true); if (bounds == null) { // Choose a sensible default value bounds = new Rectangle(0, 0, 640, 480); } // Ensure width and height are reasonable size = bounds.getSize(); if (size.width < 640) { size.width = 640; } else if (size.width > config.screen_size.width) { size.width = config.screen_size.width; } if (size.height < 480) { size.height = 480; } else if (size.height > config.screen_size.height) { size.height = config.screen_size.height; } // Set default font setUIFont(config.getFont("general.font", true), config.getFont("general.tooltip_font", true)); // Set up proxy setProxy(); // Now we set up an Authenticator Authenticator.setDefault(new GAuthenticator()); assoc_man = new FileAssociationManager(); // Create File Manager f_man = new FileManager(); // Create Collection Manager c_man = new CollectionManager(); // If there was an open collection last session, reopen it. if(open_collection == null) { open_collection = config.getString("general.open_collection", true); } if(!no_load && open_collection.length() > 0) { c_man.loadCollection(open_collection); } // Create GUI Manager (last) or else suffer the death of a thousand NPE's splash.toFront(); g_man = new GUIManager(size); g_man.display(); // Place the window in the desired location on the screen, if this is do-able (not under most linux window managers apparently. In fact you're lucky if they listen to any of your screen size requests). g_man.setLocation(bounds.x, bounds.y); g_man.setVisible(true); // After the window has been made visible, check that it is in the correct place Point location = g_man.getLocation(); int x_offset = bounds.x - location.x; int y_offset = bounds.y - location.y; // If not, offset the window to move it into the correct location if (x_offset > 0 || y_offset > 0) { g_man.setLocation(bounds.x + x_offset, bounds.y + y_offset); } // The 'after-display' triggers several events which don't occur until after the visual components are actually available on screen. Examples of these would be the various html renderings, as they can't happen offscreen. g_man.afterDisplay(); // Hide the splash. splash.hide(); splash.destroy(); splash = null; } catch (Exception error) { error.printStackTrace(); } } /** Exits the Gatherer after ensuring that things needing saving are saved. * @see java.io.FileOutputStream * @see java.io.PrintStream * @see java.lang.Exception * @see javax.swing.JOptionPane * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.collection.CollectionManager * @see org.greenstone.gatherer.gui.GUIManager */ public void exit() { exit = true; // If we have an open collection make note of it. config.setString("general.open_collection", true, null); if(c_man.ready()) { ///ystem.err.println("Collection open."); if(c_man.saved()) { ///ystem.err.println("Collection has been recently saved, so I'll remember it for next time."); config.setString("general.open_collection", true, c_man.getCollectionFilename()); } c_man.closeCollection(); } if(assoc_man != null) { assoc_man.save(); assoc_man = null; } // Store the current position and size (if reasonable) of the Gatherer for next time Rectangle bounds = g_man.getBounds(); config.setBounds("general.bounds", true, bounds); // Save configuration. saveConfig(); // Flush dictionary dictionary.destroy(); // Flush debug if(debug != null) { try { debug.flush(); debug.close(); } catch (Exception error) { error.printStackTrace(); } } // If we started a server, we should try to stop it. if(gsdlsite_cfg != null) { stopServerEXE(); } if(apps.size() == 0) { System.exit(0); } else { JOptionPane.showMessageDialog(g_man, Dictionary.get("General.Outstanding_Processes"), Dictionary.get("General.Outstanding_Processes_Title"), JOptionPane.ERROR_MESSAGE); g_man.hide(); } } /** Retrieve the metadata directory, as required by any MSMCaller implementation. * @return The currently active collection metadata directory as a String. * @see org.greenstone.gatherer.collection.CollectionManager */ public String getCollectionMetadata() { if (c_man != null && c_man.ready()) { return c_man.getCollectionMetadata(); } return ""; } /** Retrieve a reference to the frame that any dialog boxes will appear relative to, as required by any MSMCaller or CDMCaller implementation. * @return A JFrame. * @see org.greenstone.gatherer.gui.GUIManager */ public JFrame getFrame() { return g_man; } /** Method to retrieve a reference to the metadata set manager class. This is then used to create the 'metadataset' commands in the collection configuration file. * @return A reference to the MetadataSetManager. * @see org.greenstone.gatherer.collection.CollectionManager */ public MetadataSetManager getMSM() { if(c_man != null && c_man.getCollection() != null && c_man.getCollection().msm != null) { return c_man.getCollection().msm; } return null; } /** Used to 'spawn' a new child application when a file is double clicked. * @param command The command to run in the child process to start the application, garnered from the registry of a default associations file, and presented as a String. * @see java.util.Vector * @see org.greenstone.gatherer.Gatherer.ExternalApplication */ public void spawnApplication(File file) { String command = assoc_man.getCommand(file); if(command != null) { ExternalApplication app = new ExternalApplication(command); apps.add(app); app.start(); } else { ///ystem.err.println("No open command available."); } } /** The entry point into the Gatherer. Parses arguments. * @param args A collection of arguments that may include: initial screen size, dictionary, path to the GSDL etc. * @see java.io.File * @see java.io.FileInputStream * @see java.lang.Exception * @see java.util.Properties * @see org.greenstone.gatherer.Dictionary * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.gui.Splash * @see org.greenstone.gatherer.util.StaticStrings */ static public void main(String[] args) { // A serious hack, but its good enough to stop crappy 'Could not lock user prefs' error messages. // Thanks to Walter Schatz from the java forums. System.setProperty("java.util.prefs.syncInterval","2000000"); // One message every 600 hours! Gatherer gatherer = new Gatherer(); boolean debug = false; boolean no_load = false; Dictionary dictionary = new Dictionary(null, null); // Default dictionary. Only used for starting error messages. Dimension size = new Dimension(800, 540); String exec_path = null; String extra = null; String filename = null; String gsdl_path = null; String perl_path = null; // Parse arguments int argument_index = 0; String next_token = null; while(argument_index < args.length || next_token != null) { // 1. We start by attempting to parse an argument name. An argument must start with a '-', and should not contain spaces. If anything else is encountered it is ignored. String argument_name = null; if(next_token == null) { next_token = args[argument_index]; argument_index++; } if(next_token.startsWith(StaticStrings.MINUS_CHARACTER)) { // Trim second '-' just to be kind to Unixy-type people if(next_token.startsWith(StaticStrings.MINUS_CHARACTER + StaticStrings.MINUS_CHARACTER)) { argument_name = next_token.substring(1); } else { argument_name = next_token; } } next_token = null; // 2. If we now have an argument name we continue by attempting to parse a value. A value is taken to be the sequence of space seperated Strings between the last argument name and up to but not including the next argument name. Of course an argument needn't have any value (ie -debug, -help), in which case value will be null. if(argument_name != null) { String argument_value = null; StringBuffer argument_value_buffer = new StringBuffer(""); while(argument_index < args.length && next_token == null) { next_token = args[argument_index]; argument_index++; // If we just parsed an arbitary String then append it to value, followed by a single space if(!next_token.startsWith(StaticStrings.MINUS_CHARACTER)) { argument_value_buffer.append(next_token); argument_value_buffer.append(StaticStrings.SPACE_CHARACTER); next_token = null; } // If the argument token retrieved is an argument name, then leave it in next_token, which will cause the argument parsing process to move onto the next step. } // If a value now exists in argument buffer, retrieve it. Remove the last character as it will be an erroneous space. if(argument_value_buffer.length() > 0) { argument_value = argument_value_buffer.substring(0, argument_value_buffer.length() - 1); } // 3. We now have the argument name, and any associated value. We are ready to store the data in the appropriate variables. Gatherer.println("Parsed Argument: name=" + argument_name + (argument_value != null ? (", value=" + argument_value) : ", no value")); // 3a. First those arguments that have no associated value if(argument_value == null) { if(argument_name.equals(StaticStrings.HELP_ARGUMENT)) { printUsage(dictionary); System.exit(0); } // Run GLI in debug mode. Produces debug log plus extra messages. else if(argument_name.equals(StaticStrings.DEBUG_ARGUMENT)) { debug = true; } // Forces no loading on previous collection. else if(argument_name.equals(StaticStrings.NO_LOAD_ARGUMENT)) { no_load = true; filename = null; } // Specify the use of Greenstone LAF. else if(argument_name.equals(StaticStrings.SKIN_ARGUMENT)) { // SkinLF try { SkinLookAndFeel.setSkin(SkinLookAndFeel.loadThemePackDefinition(SkinUtils.toURL(new File(SKIN_DEFINITION_FILE)))); SkinLookAndFeel.enable(); } catch (Exception error) { ///ystem.err.println("Error: " + error); error.printStackTrace(); } } } // 3b. Now for those that do else { // Parse the path to the GSDL. Required argument. if(argument_name.equals(StaticStrings.GSDL_ARGUMENT)) { if(argument_value.endsWith(File.separator)) { gsdl_path = argument_value; } else { gsdl_path = argument_value + File.separator; } } // Specify a collection to load initially. Could be used for file associations. else if(argument_name.equals(StaticStrings.LOAD_ARGUMENT)) { filename = argument_value; no_load = false; } // Parse the url to a running web server, or a file path to the local library server. else if(argument_name.equals(StaticStrings.LIBRARY_ARGUMENT)) { exec_path = argument_value; // If there is no colon in first five characters of the exec_path (which would either be an existing protocol, or a windows file path to say a local library), we can append the protocol http://. if(argument_value.lastIndexOf(StaticStrings.COLON_CHARACTER, 5) == -1) { exec_path = StaticStrings.HTTP_PROTOCOL_STR + argument_value; } else { exec_path = argument_value; } // If the user has given us an address, but it ends with a '/' we assume we're using the greenstone library.cgi if(exec_path.startsWith(StaticStrings.HTTP_PROTOCOL_STR) && exec_path.endsWith(StaticStrings.URL_SEPARATOR_CHARACTER)) { exec_path = exec_path + StaticStrings.LIBRARY_STR; } } // Parse the path to PERL. If not provided its assumes perl should be availble on the PATH. else if(argument_name.equals(StaticStrings.PERL_ARGUMENT)) { perl_path = argument_value; // Test whether this points to the Perl bin directory or the Perl executable itself. File perl_file = new File(perl_path); if(perl_file.isDirectory()) { // If this is windows we create a child file perl.exe, otherwise we create perl if(Utility.isWindows()) { perl_file = new File(perl_file, Utility.PERL_EXECUTABLE_WINDOWS); } else { perl_file = new File(perl_file, Utility.PERL_EXECUTABLE_UNIX); } // And store this new path. perl_path = perl_file.getAbsolutePath(); perl_file = null; } // Otherwise its fine as it is } } } // Argument name was null, nothing to be done. } next_token = null; // Arguments all parsed. // Splash screen. Splash splash = new Splash(); dictionary.destroy(); gatherer.run(size, gsdl_path, exec_path, debug, perl_path, no_load, splash, filename); } /** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer. */ static public void missingEXEC(Dictionary dictionary) { WarningDialog dialog = new WarningDialog("warning.MissingEXEC", "general.exec_address"); dialog.setValueField(new URLField(Gatherer.config.getColor("coloring.editable_foreground", false), Gatherer.config.getColor("coloring.editable_background", false), Gatherer.config.getColor("coloring.error_foreground", false), Gatherer.config.getColor("coloring.error_background", false))); dialog.display(); dialog.dispose(); dialog = null; ///ystem.out.println(Dictionary.get("General.Missing_EXEC")); } /** Prints a warning message about a missing GSDL path, which although not fatal pretty much ensures nothing will work properly in the Gatherer. */ static public void missingGSDL(Dictionary dictionary) { WarningDialog dialog = new WarningDialog("warning.MissingGSDL", false); dialog.display(); dialog.dispose(); dialog = null; ///ystem.out.println(Dictionary.get("General.Missing_GSDL")); } /** Prints a warning message about a missing PERL path, which although not fatal pretty much ensures no collection creation/building will work properly in the Gatherer. */ static public void missingPERL(Dictionary dictionary) { WarningDialog dialog = new WarningDialog("warning.MissingPERL", false); dialog.display(); dialog.dispose(); dialog = null; ///ystem.out.println(Dictionary.get("General.Missing_PERL")); } /** Print a message to the debug stream. */ static synchronized public void println(String message) { if(debug != null) { debug.println(message); System.err.println(message); } } /** Print a stack trace to the debug stream. */ static synchronized public void printStackTrace(Exception exception) { if(debug != null) { exception.printStackTrace(debug); exception.printStackTrace(); } } /** Prints a usage message to screen. */ static public void printUsage(Dictionary dictionary) { System.out.println(Dictionary.get("General.Usage")); } /** Sets up the proxy connection by setting JVM Environment flags and creating a new Authenticator. * @see java.lang.Exception * @see java.lang.System * @see java.net.Authenticator * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.GAuthenticator */ static public void setProxy() { try {// Can throw several exceptions if(Gatherer.config.get("general.use_proxy", true)) { System.setProperty("http.proxyType", "4"); System.setProperty("http.proxyHost", Gatherer.config.getString("general.proxy_host", true)); System.setProperty("http.proxyPort", Gatherer.config.getString("general.proxy_port", true)); System.setProperty("http.proxySet", "true"); } else { System.setProperty("http.proxySet", "false"); } } catch (Exception error) { Gatherer.println("Error in Gatherer.initProxy(): " + error); Gatherer.printStackTrace(error); } } /** Set all the default fonts of the program to a choosen font, except the tooltip font which should be some fixed width font. * @param f The default font to use in the Gatherer as a FontUIResource. * @param ttf The tooltip font to use also as a FontUIResource. * @see java.util.Enumeration * @see javax.swing.UIManager */ static public void setUIFont (FontUIResource f, FontUIResource ttf){ // sets the default font for all Swing components. // ex. // setUIFont (new FontUIResource("Serif",Font.ITALIC,12)); // Enumeration keys = UIManager.getDefaults().keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); Object value = UIManager.get (key); if (value instanceof FontUIResource) UIManager.put (key, f); } // Now set the tooltip font to some fixed width font UIManager.put("ToolTip.font", ttf); } /** Loads the configuration file if one exists. Otherwise it creates a new one. Currently uses serialization. * @param size The desired size of the Gatherer window as a Dimension. * @param gsdl_path The path to the gsdl directory, gathered from the startup arguments, and presented as a String. * @param exec_path The path to the library executable, gathered from the startup arguments, and presented as a String. * @param perl_path The path to the PERL compiler as a String. Necessary for windows platform versions. * @see java.io.FileInputStream * @see java.io.ObjectInputStream * @see java.lang.Exception * @see org.greenstone.gatherer.Configuration */ private void loadConfig(String gsdl_path, String exec_path, String perl_path) { try { config = new Configuration(gsdl_path, exec_path, perl_path); } catch (Exception error) { Gatherer.println("config.xml is not a well formed XML document."); Gatherer.printStackTrace(error); } } /** Causes the general configuration file to export itself to xml. Doesn't effect any remaining collection configuration, as its up to the collection manager to handle them (especially since we have no idea where they are going). */ private void saveConfig() { try { config.save(); } catch (Exception error) { Gatherer.printStackTrace(error); } } private void startServerEXE() { if(config.exec_file != null && config.exec_address == null && Utility.isWindows()) { // First of all we create a GSDLSiteCFG object and check if a URL is already present, in which case the server is already running. gsdlsite_cfg = new GSDLSiteConfig(config.exec_file); String url = gsdlsite_cfg.getURL(); // If its already running then set exec address. if(url != null) { try { config.exec_address = new URL(url); } catch(Exception error) { } } // Otherwise its time to run the server in a spawned process. if(config.exec_address == null && config.exec_file.exists()) { // Configure for immediate entry. Note that this only works if the gsdlsite.cfg file exists. gsdlsite_cfg.set(); // Spawn server String command = config.exec_file.getAbsolutePath() + " " + gsdlsite_cfg.getSiteConfigFilename(); server = new ExternalApplication(command); server.start(); command = null; // Now we have to wait until program has started. We do this by reloading and checking ///ystem.err.print("Waiting until the local library has loaded"); try { gsdlsite_cfg.load(); int try_again = JOptionPane.YES_OPTION; int attempt_count = 0; while(gsdlsite_cfg.getURL() == null && try_again == JOptionPane.YES_OPTION) { ///ystem.err.print("."); if(attempt_count == 60) { try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.QuitTimeOut"), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION); } else { synchronized(this) { wait(1000); // Wait one second (give or take) } gsdlsite_cfg.load(); attempt_count++; } } if((url = gsdlsite_cfg.getURL()) != null) { // Ta-da. Now the url should be available. config.exec_address = new URL(url); // A quick test involves opening a connection to get the home page for this collection. If this fails then we try changing the url to be localhost. try { Gatherer.println("Try connecting to server on config url: '" + config.exec_address + "'"); URLConnection connection = config.exec_address.openConnection(); connection.getContent(); } catch(IOException bad_url_connection) { try { Gatherer.println("Try connecting to server on local host: '" + gsdlsite_cfg.getLocalHostURL() + "'"); config.exec_address = new URL(gsdlsite_cfg.getLocalHostURL ()); URLConnection connection = config.exec_address.openConnection(); connection.getContent(); } catch(IOException worse_url_connection) { Gatherer.println("Can't connect to server on either address."); config.exec_address = null; config.exec_file = null; } } } // Unable to start local library. Show appropriate message. else { missingEXEC(dictionary); } } catch (Exception error) { error.printStackTrace(); } } // Can't do a damb thing. } Gatherer.println("Having started server.exe, exec_address is: " + config.exec_address); } private void stopServerEXE() { if(server != null && config.exec_address != null) { // See if its already exited for some reason. gsdlsite_cfg.load(); if(gsdlsite_cfg.getURL() != null) { // Send the command for it to exit. Gatherer.g_man.preview_pane.configServer(GSDLSiteConfig.QUIT_COMMAND); // Wait until it exits. try { gsdlsite_cfg.load(); int try_again = JOptionPane.YES_OPTION; int attempt_count = 0; while(gsdlsite_cfg.getURL() != null && try_again == JOptionPane.YES_OPTION) { if(attempt_count == 60) { try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.QuitTimeOut"), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION); } else { synchronized(this) { wait(1000); // Wait one second (give or take) } gsdlsite_cfg.load(); attempt_count++; } } //if(gsdlsite_cfg.getURL() != null) { //JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Server.QuitManual"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); //} } catch (Exception error) { error.printStackTrace(); } } // Restore the gsdlsite_cfg. if(gsdlsite_cfg != null) { gsdlsite_cfg.restore(); } // If the local server is still running then our changed values will get overwritten. if(gsdlsite_cfg.getURL() != null) { JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Server.QuitFailed"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); } gsdlsite_cfg = null; server = null; } } /** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */ private class ExternalApplication extends Thread { private Process process = null; /** The initial command string given to this sub-process. */ private String command = null; private String[] commands = null; /** Constructor. * @param command The initial command String. */ public ExternalApplication(String command) { this.command = command; } public ExternalApplication(String[] commands) { this.commands = commands; } /** We start the child process inside a new thread so it doesn't block the rest of Gatherer. * @see java.lang.Exception * @see java.lang.Process * @see java.lang.Runtime * @see java.lang.System * @see java.util.Vector */ public void run() { // Call an external process using the args. try { if(commands != null) { StringBuffer whole_command = new StringBuffer(); for(int i = 0; i < commands.length; i++) { whole_command.append(commands[i]); whole_command.append(" "); } println("Running " + whole_command.toString()); Runtime rt = Runtime.getRuntime(); process = rt.exec(commands); process.waitFor(); } else { println("Running " + command); Runtime rt = Runtime.getRuntime(); process = rt.exec(command); process.waitFor(); } } catch (Exception error) { println("Error in ExternalApplication.run(): " + error); printStackTrace(error); } // Remove ourself from Gatherer list of threads. apps.remove(this); // Call exit if we were the last outstanding child process thread. if(apps.size() == 0 && exit == true) { stopServerEXE(); System.exit(0); } } public void stopExternalApplication() { if(process != null) { process.destroy(); } } } /** This class is intented to detect a specific SIGNAL, in this case SIGINT, and exit properly, rather than letting the Gatherer be interrupted which has the potential to leave erroneous lock files. */ private class CTRLCHandler implements SignalHandler { /** true if we ignore any other signals we receive, most likely because we are already dealing with one, false otherwise. */ private boolean ignore = false; /** The method called by the system to inform us a signal has occured. * @param sig The Signal itself. * @see org.greenstone.gatherer.collection.CollectionManager */ public void handle(Signal sig) { if(!ignore) { ignore = true; // handle SIGINT System.out.println("Caught Ctrl-C..."); if(c_man != null && c_man.ready()) { c_man.closeCollection(); } exit(); ignore = false; } } } private class PerlTest { private String[] command = new String[2]; public PerlTest() { if(Utility.isWindows()) { command[0] = Utility.PERL_EXECUTABLE_WINDOWS; } else { command[0] = Utility.PERL_EXECUTABLE_UNIX; } command[1] = "-version"; } public boolean found() { boolean found = false; try { Process prcs = Runtime.getRuntime().exec(command); prcs.waitFor(); found = (prcs.exitValue() == 0); prcs = null; } catch(Exception error) { } return found; } public String toString() { return command[0]; } } }