package org.greenstone.gatherer; /** *######################################################################### * * 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. *######################################################################## */ import com.l2fprod.gui.*; import com.l2fprod.gui.plaf.skin.*; import com.l2fprod.util.*; import java.awt.*; import java.io.*; import java.lang.*; import java.net.*; import java.util.*; import javax.swing.*; import javax.swing.plaf.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.GAuthenticator; 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.WarningDialog; import org.greenstone.gatherer.msm.MetadataSetManager; import org.greenstone.gatherer.util.ArrayTools; import org.greenstone.gatherer.util.GSDLSiteConfig; 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 */ public class Gatherer { /** 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; /** A public reference to the message log. */ static public Log log; /** 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"; /** 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 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 bloddy 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 { // Create log log = new Log(); // 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 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) { missingEXEC(dictionary); } if(gsdl_path == null) { missingGSDL(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(); } } /** Writes a message to the debug filestream. * @param message The message as a String. */ // public void debug(String message) { //debug(null, message); // } /** Writes a message to the debug filestream. * @param error The Exception associated with this message, or null for no exception. * @param message The message as a String. * @see java.io.FileOutputStream * @see java.io.PrintStream * @see java.lang.Exception */ // public void debug(Exception exception, String message) { //if(message != null) { // Gatherer.println(message); //} //if(exception != null) { // Gatherer.printStackTrace(exception); //} // } /** 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.destroy(); 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 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, get("General.Outstanding_Processes"), get("General.Outstanding_Processes_Title"), JOptionPane.ERROR_MESSAGE); g_man.hide(); } } /** Overloaded to call get with both a key and an empty argument array. * @param key A String which is mapped to a initial String within the ResourceBundle. * @return A String which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call. */ public String get(String key) { return dictionary.get(key, (String[])null); } /** Overloaded to call get with both a key and an argument array with one element. * @param key A String which is mapped to a initial String within the ResourceBundle. * @param arg A single argument as a String. * @return A String which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatiically populated with formatting Strings of with argument String provided in the get call. */ public String get(String key, String arg) { String args[] = new String[1]; args[0] = arg; return dictionary.get(key, args); } /** Used to retrieve a property value from the Locale specific ResourceBundle, based upon the key and arguments supplied. If the key cannot be found or if some other part of the call fails a default (English) error message is returned.
* Here the get recieves a second argument which is an array of Strings used to populate argument fields, denoted {n}, within the value String returned. Note that argument numbers greater than or equal to 32 are automatically mapped to the formatting String named Fargn. * @param key A String which is mapped to a initial String within the ResourceBundle. * @param args A String[] used to populate argument fields within the complete String. * @return A String which has been referenced by the key String and that either contains no argument fields, or has had the argument fields automatically populated with formatting Strings of with argument String provided in the get call. * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.Dictionary */ public String get(String key, String args[]) { return dictionary.get(key, args); } /** 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."); } } /** Some startup arguments to the Gatherer have been encoded, where ' ' is replaced with '%', in order to allow arguments containing spaces to be parsed correctly by the JVM, and this method restores these arguments to thier original state. * @param encoded An encoded String. * @return The decoded String. */ static public String decode(String encoded) { return encoded.replace('%', ' '); } /** 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 */ 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 for(int i = 0; i < args.length; i++) { if(args[i].equals("-gsdl")) { gsdl_path = decode(args[i+1]); if(!gsdl_path.endsWith(File.separator)) { gsdl_path = gsdl_path + File.separator; } } if(args[i].equals("-load")) { filename = decode(args[i+1]); } else if(args[i].equals("-library") && (i + 1) < args.length) { exec_path = args[i+1]; // If we are on a non-windows system, and thus the local server is unavailable, we can append http:// if no protocol found. if(exec_path.lastIndexOf(":", 5) == -1) { exec_path = "http://" + exec_path; } // 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("http://") && exec_path.endsWith("/")) { exec_path = exec_path + "library"; } } else if(args[i].equals("-perl")) { perl_path = decode(args[i+1]); // 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 ///ystem.err.println("Perl executable is: " + perl_path); } else if(args[i].equals("--help") || args[i].equals("-help") || args[i].equals("?") || args[i].equals("/?") || args[i].equals("/help")) { printUsage(dictionary); System.exit(0); } else if(args[i].equals("--debug") || args[i].equals("-debug")) { debug = true; } // Don't load any previous collection. Convenient for me else if(args[i].equals("-no_load")) { no_load = true; } // Check if they want it skinned. else if(args[i].equals("-skinlf")) { // SkinLF try { SkinLookAndFeel.setSkin(SkinLookAndFeel.loadThemePackDefinition(SkinUtils.toURL(new File("lib/greenaqua/greenaqua.xml")))); SkinLookAndFeel.enable(); } catch (Exception error) { ///ystem.err.println("Error: " + error); error.printStackTrace(); } } } // Splash screen. Splash splash = new Splash(); // We take appropriate action when an empty gsdl_path is detected. if(gsdl_path == null) { // Check for the presence of the path.cfg file. File path_file = new File("path.cfg"); if(path_file.exists()) { try { // Read in then add each property to the Java Environment. FileInputStream prop_file = new FileInputStream(path_file); Properties p = new Properties(); p.load(prop_file); extra = p.getProperty(KEY); } catch (Exception error) { System.out.println("Error in main():"); error.printStackTrace(); System.exit(1); } } else { missingGSDL(dictionary); } } // We take appropriate action when an empty bin_path is detected. 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", 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); } else { 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); } else { 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); } } /** Creates and dispatches a message given the initial details. * @param level An int indicating the message level for this message. * @param message A String which contains the payload of this message. * @see org.greenstone.gatherer.Message * @see org.greenstone.gatherer.Log */ private void message(int level, String message) { Message msg = new Message(Message.GENERAL, level, message); log.add(msg); } /** 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 server = new ExternalApplication(config.exec_file.getAbsolutePath()); server.start(); // Now we have to wait until program has started. We do this by reloading and checking try { gsdlsite_cfg.load(); while((url = gsdlsite_cfg.getURL()) == null) { synchronized(this) { wait(1000); } gsdlsite_cfg.load(); } // Ta-da. Now the url should be available. config.exec_address = new URL(url); } 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) { // 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; while(try_again == JOptionPane.YES_OPTION) { int attempt_count = 0; while(gsdlsite_cfg.getURL() != null && attempt_count < 60) { synchronized(this) { wait(1000); // Wait one second (give or take) } gsdlsite_cfg.load(); attempt_count++; } if(attempt_count == 60) { try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, dictionary.get("Server.QuitTimeOut"), dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION); } else { try_again = JOptionPane.NO_OPTION; } } 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; /** Constructor. * @param command The initial command String. */ public ExternalApplication(String command) { this.command = command; } /** 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 { 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]; } } }