/** *############################################################################ * A component of the Greenstone Librarian Interface, part of the Greenstone * digital library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: Michael Dewsnip, NZDL Project, University of Waikato, NZ * * Copyright (C) 2004 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.greenstone; import java.io.*; import java.lang.*; import java.net.*; import java.util.*; import javax.swing.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.util.PortFinder; import org.greenstone.gatherer.util.Utility; public class LocalLibraryServer { static final private int WAITING_TIME = 20; // number of seconds to wait for the server to start and stop static final private String ADD_COMMAND = "?a=config&cmd=add-collection&c="; static final private String RELEASE_COMMAND = "?a=config&cmd=release-collection&c="; static final private String QUIT_COMMAND = "?a=config&cmd=kill"; static private LLSSiteConfig llssite_cfg_file = null; static private File local_library_server_file = null; static private boolean running = false; static private String ID = "greenstone-server"; // a sort of process ID // Need to use sockets to tell the server program to terminate when on Linux // The socket port number that we will use to communicate the termination static private int port; // The server is persistent if it does not have to reload all the values // over and over again each time. Tomcat is persistent and fastcgi is too, // but the Apache webserver is not persistent by default. // Change the initialisation of this value depending on whether fastcgi is // on. At the moment, this does not apply to the Linux' local library server. static private boolean isPersistentServer = Utility.isWindows(); static public void addCollection(String collection_name) { if (isPersistentServer) { config(ADD_COMMAND + collection_name); } } // Used to send messages to the local library static private void config(String command) { if (!isPersistentServer) { return; } if (Configuration.library_url == null) { System.err.println("Error: Trying to configure local library with null Configuration.library_url!"); return; } try { URL url = new URL(Configuration.library_url.toString() + command); HttpURLConnection library_connection = (HttpURLConnection) url.openConnection(); // It's very important that we read the output of the command // This ensures that the command has actually finished // (The response code is returned immediately) InputStream library_is = library_connection.getInputStream(); BufferedReader library_in = new BufferedReader(new InputStreamReader(library_is, "UTF-8")); String library_output_line = library_in.readLine(); while (library_output_line != null) { DebugStream.println("Local library server output: " + library_output_line); library_output_line = library_in.readLine(); } library_in.close(); int response_code = library_connection.getResponseCode(); if (response_code >= HttpURLConnection.HTTP_OK && response_code < HttpURLConnection.HTTP_MULT_CHOICE) { DebugStream.println("200 - Complete."); } else { DebugStream.println("404 - Failed."); } } catch (Exception exception) { DebugStream.printStackTrace(exception); } } // Used to send messages to the local server on Linux static private boolean sendMessageToServer(String message) { if(Utility.isWindows()) { return false; } if(port == -1) { return false; } try { Socket clientSocket = new Socket("localhost", port); Writer writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); writer.write(message); writer.close(); writer = null; } catch (Exception e) { System.err.println("An exception occurred when trying to send the message: " + message + "\nto the LocalLibraryServer.\n" + e); return false; } return true; } static public boolean isRunning() { if (!running) return false; // if the url is pending, then running would also be false (server not started up yet) llssite_cfg_file.load(true); String url = llssite_cfg_file.getURL(); if (url == null) return false; // Called by Gatherer to check whether we need to stop the server. // if the url is pending, then the GSI hasn't started the server up yet // Already covered in !running //if (url.equals(LLSSiteConfig.URL_PENDING)) return false; return true; } static public void releaseCollection(String collection_name) { if (isPersistentServer) { config(RELEASE_COMMAND + collection_name); } } static public void start(String gsdl_path, String local_library_server_file_path) { // Check the local library server file (server.exe or gs2-server.sh) exists local_library_server_file = new File(local_library_server_file_path); if (!local_library_server_file.exists()) { DebugStream.println("No local library at given file path."); String defaultServerFilename = Utility.isWindows() ? "server.exe" : "gs2-server.sh"; local_library_server_file = new File(gsdl_path + defaultServerFilename); if (!local_library_server_file.exists()) { DebugStream.println("No local library at all."); return; } } // In the case of the Local Library Server on Linux, we need to do an extra test: // If GS2 was not configured with --enable-apache-httpd, then there is no apache webserver folder, // even though the gs2-server.sh file would still be there. That means if the folder is absent // we still have no local library server. if(!Utility.isWindows()) { File localLinuxServerFolder = new File(gsdl_path, "apache-httpd"); if (!localLinuxServerFolder.exists() && !localLinuxServerFolder.isDirectory()) { DebugStream.println("The web server does not exist at " + localLinuxServerFolder.getAbsolutePath() + "\nNo local library at all. Trying web library"); return; } } llssite_cfg_file = new LLSSiteConfig(local_library_server_file); // If the user launched the GSI independent of GLI, but user has not pressed // Enter Library yet, then we will obtain the previewURL later. if(LocalLibraryServer.isURLPending()) { // running is still false when the URL is pending because only GSI is running, not the server return; } else if(llssite_cfg_file.isIndependentGSI()) { // there is a url, it's not pending, and it is llssite.cfg: meaning GSI has started up return; } // Spawn local library server process String local_library_server_command = local_library_server_file.getAbsolutePath() + getExtraLaunchArguments(llssite_cfg_file); // Check if the server is already running String url = llssite_cfg_file.getURL(); if (url != null) { // If it is already running then set the Greenstone web server address and we're done // E.g. if previously GLI was not properly shut down, the URL property (signifying // the server is still running) would still be in the glisite_cfg file. try { Configuration.library_url = new URL(url); running = true; // Run the server interface Gatherer.spawnApplication(local_library_server_command, ID); return; } catch (MalformedURLException exception) { DebugStream.printStackTrace(exception); } } // Configure the server for immediate entry //llssite_cfg_file.set(); // Spawn local library server process Gatherer.spawnApplication(local_library_server_command, ID); // Wait until program has started try { testServerRunning(); // will set running = true when the server is up and running successfully } catch (IOException bad_url_connection) { try { // If this fails then we try changing the url to be localhost Configuration.library_url = new URL(llssite_cfg_file.getLocalHostURL()); DebugStream.println("Try connecting to server on local host: '" + Configuration.library_url + "'"); URLConnection connection = Configuration.library_url.openConnection(); connection.getContent(); running = true; } catch (IOException worse_url_connection) { DebugStream.println("Can't connect to server on either address."); Configuration.library_url = null; running = false; } } } static public void stop() { if (!running) { // also the case if the URL is pending in an independently launched GSI return; } // don't (can't) shutdown the GSI/server if it was launched independent of GLI if(llssite_cfg_file.isIndependentGSI()) { return; } // Send the command for it to exit. if (isPersistentServer) { config(QUIT_COMMAND); } else { if(sendMessageToServer("QUIT")) { Gatherer.terminateApplication(ID); } else { System.err.println("Unable to stop the server, since there's no communication port to send the quit msg over." + "\nPlease stop the local Greenstone server manually."); } } // Wait until program has stopped, by reloading and checking the URL field llssite_cfg_file.load(false); int attempt_count = 0; String url = llssite_cfg_file.getURL(); while (url != null && !url.equals(LLSSiteConfig.URL_PENDING)) { // if pending, the server is already stopped (not running) new OneSecondWait(); // Wait one second (give or take) llssite_cfg_file.load(false); attempt_count++; // After waiting for the specified time, ask the user whether they want to wait for that long again if (attempt_count == WAITING_TIME) { int try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.QuitTimeOut", Integer.toString(WAITING_TIME)), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION); if (try_again == JOptionPane.NO_OPTION) { return; } attempt_count = 0; } // read the url again to see if it's updated url = llssite_cfg_file.getURL(); } // Restore the llssite_cfg. llssite_cfg_file.restore(); // If the local server is still running then our changed values will get overwritten. url = llssite_cfg_file.getURL(); if (url != null && !url.equals(LLSSiteConfig.URL_PENDING)) { JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Server.QuitManual"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); } running = false; } static private String getExtraLaunchArguments(LLSSiteConfig site_cfg_file) { String args = " " + site_cfg_file.getSiteConfigFilename(); if(Utility.isWindows()) { return args; } // Else, when running the Local Library Server on Linux, need to provide a port argument try { PortFinder portFinder = new PortFinder(50100, 100); port = portFinder.findPortInRange(false); // silent mode } catch(Exception e) { System.err.println("Exception when trying to find an available port: " + e); port = -1; } return args + " --quitport=" + port; } // This method first tests whether there is a URL in the llssite_cfg_file // and after that appears, it tests whether the URL is functional. static private void testServerRunning() throws IOException { // Wait until program has started, by reloading and checking the URL field llssite_cfg_file.load(false); int attempt_count = 0; while (llssite_cfg_file.getURL() == null) { new OneSecondWait(); // Wait one second (give or take) llssite_cfg_file.load(false); attempt_count++; // After waiting for the specified time, ask the user whether they want to wait for that long again if (attempt_count == WAITING_TIME) { int try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.StartUpTimeOut", Integer.toString(WAITING_TIME)), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION); if (try_again == JOptionPane.NO_OPTION) { return; } attempt_count = 0; } } // Ta-da. Now the url should be available try { Configuration.library_url = new URL(llssite_cfg_file.getURL()); } catch (MalformedURLException exception) { DebugStream.printStackTrace(exception); } // A quick test involves opening a connection to get the home page for this collection try { DebugStream.println("Try connecting to server on config url: '" + Configuration.library_url + "'"); URLConnection connection = Configuration.library_url.openConnection(); connection.getContent(); running = true; } catch (IOException bad_url_connection) { throw bad_url_connection; } } /** Returns true if we're waiting on the user to click on Enter Library button * in an independently launched GSI (one not launched by GLI). */ static public boolean isURLPending() { /*if(Configuration.library_url != null) { System.err.println("**** Configuration.library_url: " + Configuration.library_url ); return false; // don't need to do anything as we already have the url }*/ llssite_cfg_file.load(true); // don't force reload, load only if modified String url = llssite_cfg_file.getURL(); if(url != null) { if(url.equals(LLSSiteConfig.URL_PENDING)) { running = false; // imagine if they restarted an external GSI return true; } else { // a valid URL at last try { Configuration.library_url = new URL(url); running = true; } catch (MalformedURLException exception) { exception.printStackTrace(); DebugStream.printStackTrace(exception); } return false; } } // If either the URL is null--which means no independent GSI (Greenstone server interface // app) was launched--or if the 'URL' doesn't say it's pending, then we're not waiting return false; } static public void checkServerRunning() { if (!running) return; // don't worry about it if it's not supposed to be running llssite_cfg_file.load(true); // don't force reload, load only if modified String url = llssite_cfg_file.getURL(); if(url != null) { try { Configuration.library_url = new URL(url); } catch (MalformedURLException exception) { DebugStream.printStackTrace(exception); exception.printStackTrace(); } } else { // need to restart the server again // if we were running an independently launched GSI before, but the GSI has been // exited, it would need to be relaunched, but from within GLI (dependent) this time. if(llssite_cfg_file.isIndependentGSI()) { // first save the current configFile (llssite.cfg), then set to use glisite.cfg llssite_cfg_file.save(); llssite_cfg_file.relaunchAsDependentGSI(local_library_server_file); } else { // We were using glisite.cfg before, don't need to swap config files llssite_cfg_file.set(); } // Spawn local library server process String local_library_server_command = local_library_server_file.getAbsolutePath() + getExtraLaunchArguments(llssite_cfg_file); running = false; Gatherer.spawnApplication(local_library_server_command, ID); try { testServerRunning(); // don't return until the webserver is up and running } catch (IOException bad_url_connection) { DebugStream.println("Can't connect to server on address " + Configuration.library_url); running = false; } } } static private class OneSecondWait { public OneSecondWait() { synchronized(this) { try { wait(1000); } catch (InterruptedException exception) { } } } } static public class LLSSiteConfig extends LinkedHashMap { private File configFile; private File llssite_cfg; private File glisite_cfg; private String autoenter_initial; private String start_browser_initial; private long lastModified = 0; static final private String AUTOENTER = "autoenter"; static final private String COLON = ":"; static final private String ENTERLIB = "enterlib"; static final private String FALSE = "0"; static final private String GLISITE_CFG = "glisite.cfg"; static final private String GSDL = "greenstone"; // httpprefix is no longer /gsdl but /greenstone static final private String LLSSITE_CFG = "llssite.cfg"; static final private String LOCAL_HOST = "http://localhost"; static final private String PORTNUMBER = "portnumber"; static final private String SEPARATOR = "/"; static final private String SPECIFIC_CONFIG = "--config="; static final private String STARTBROWSER = "start_browser"; static final private String TRUE = "1"; static final private String URL = "url"; static final public String URL_PENDING = "URL_pending"; public LLSSiteConfig(File server_exe) { debug("New LLSSiteConfig for: " + server_exe.getAbsolutePath()); llssite_cfg = new File(server_exe.getParentFile(), LLSSITE_CFG); glisite_cfg = new File(server_exe.getParentFile(), GLISITE_CFG); configFile = null; autoenter_initial = null; start_browser_initial = null; // first test if server was started independently of GLI // if so, the config file we'd be using would be llssite.cfg if(!usingLLS_configFile()) { // if we were using llssite_cfg, this would have loaded it in // else we try using the glisite configfile useGLISiteCfg(server_exe); } if(configFile != null && configFile.exists()) { System.err.println("Using configfile: " + configFile); lastModified = configFile.lastModified(); } } /** Tries to get a glisite.cfg file and then loads it, setting it as the configFile */ public void useGLISiteCfg(File server_exe) { if(!glisite_cfg.exists()) { // create it from the templates or the llssite.cfg file File llssite_cfg_in = new File(server_exe.getParentFile(), LLSSITE_CFG+".in"); File glisite_cfg_in = new File(server_exe.getParentFile(), GLISITE_CFG+".in"); // need to generate glisite_cfg from glisite_cfg_in, llssite_cfg or llssite.cfg.in if(glisite_cfg_in.exists()) { copyConfigFile(glisite_cfg_in, glisite_cfg, false); } else if(llssite_cfg_in.exists()) { copyConfigFile(llssite_cfg_in, glisite_cfg_in, true); // adjust for glisite.cfg copyConfigFile(glisite_cfg_in, glisite_cfg, false); } else if(llssite_cfg.exists()) { copyConfigFile(llssite_cfg, glisite_cfg, true); // adjust for glisite.cfg } else { debug("Neither the file glisite.cfg nor llssite.cfg can be found!"); } } // use the config file now if(glisite_cfg.exists()) { configFile = glisite_cfg; load(false); // force reload } } /** Tests whether the server interface is up, running independently of GLI * If so, we don't need to launch the server interface. * The server interface may not have started up the server itself though * (in which case the server URL would be URL_PENDING). * This method returns true if the server interface has already started * and, if so, it would have loaded in the llssite_cfg configFile. */ private boolean usingLLS_configFile() { if(!llssite_cfg.exists()) { return false; } // check if the configfile contains the URL line configFile = llssite_cfg; load(false); // force load if(getURL() == null) { configFile = null; clear(); // we're not using llssite_cfg, so clear the values we just read return false; } //System.err.println("***** we're using llssite_configfile, url:" + getURL()); return true; } /** @return true if GSI was started up independently and outside of GLI. * In such a case, GLI would be using llssite_cfg. */ public boolean isIndependentGSI() { return (configFile == llssite_cfg); } /** Call this when an independently launched GSI server has been stopped * (one using llssite_cfg) and GLI needs to next launch another server. * In such a case, since GLI itself is relaunching the GSI, we use glisite_cfg. */ public void relaunchAsDependentGSI(File server_exe) { useGLISiteCfg(server_exe); } public boolean exists() { return configFile.exists(); } public String getLocalHostURL() { StringBuffer url = new StringBuffer(LOCAL_HOST); url.append(COLON); url.append((String)get(PORTNUMBER)); String enterlib = (String)get(ENTERLIB); if(enterlib == null || enterlib.length() == 0) { // Use the default /gsdl and hope for the best. url.append(SEPARATOR); url.append(GSDL); } else { if(!enterlib.startsWith(SEPARATOR)) { url.append(SEPARATOR); } url.append(enterlib); } enterlib = null; debug("Found Local Library Address: " + url.toString()); return url.toString(); } /** @return the cmd-line parameter for the configfile used to launch * the server through GLI: --config . */ public String getSiteConfigFilename() { return SPECIFIC_CONFIG + configFile.getAbsolutePath(); } public String getURL() { // URL is made from url and portnumber String url = (String) get(URL); // server interface is already up, independent of GLI // but it has not started the server (hence URL is pending) if(url != null && url.equals(URL_PENDING)) { return url; } if(!Utility.isWindows()) { return url; } if(url != null) { StringBuffer temp = new StringBuffer(url); temp.append(COLON); temp.append((String)get(PORTNUMBER)); String enterlib = (String)get(ENTERLIB); if(enterlib == null || enterlib.length() == 0) { // Use the default /greenstone prefix and hope for the best. temp.append(SEPARATOR); temp.append(GSDL); } else { if(!enterlib.startsWith(SEPARATOR)) { temp.append(SEPARATOR); } temp.append(enterlib); } enterlib = null; url = temp.toString(); } debug("Found Local Library Address: " + url); return url; } public boolean isModified() { return (lastModified != configFile.lastModified()); } public void load(boolean reloadOnlyIfModified) { if(configFile == null) { debug(configFile.getAbsolutePath() + " cannot be found!"); } if(isModified()) { lastModified = configFile.lastModified(); } else if(reloadOnlyIfModified) { return; // asked to reload only if modified. Don't reload since not modified } if(configFile.exists()) { debug("Load: " + configFile.getAbsolutePath()); clear(); try { BufferedReader in = new BufferedReader(new FileReader(configFile)); String line = null; while((line = in.readLine()) != null) { String key = null; String value = null; int index = -1; if((index = line.indexOf("=")) != -1 && line.length() >= index + 1) { key = line.substring(0, index); value = line.substring(index + 1); } else { key = line; } put(key, value); } in.close(); } catch (Exception error) { error.printStackTrace(); } } else { debug(configFile.getAbsolutePath() + " cannot be found!"); } } /** Restore the autoenter value to its initial value, and remove url if present. */ public void restore() { if(configFile != null) { // Delete the file configFile.delete(); } else { debug("Restore Initial Settings"); put(AUTOENTER, autoenter_initial); put(STARTBROWSER, start_browser_initial); remove(URL); save(); } } public void set() { debug("Set Session Settings"); if(autoenter_initial == null) { autoenter_initial = (String) get(AUTOENTER); debug("Remember autoenter was: " + autoenter_initial); } put(AUTOENTER, TRUE); if(start_browser_initial == null) { start_browser_initial = (String) get(STARTBROWSER); debug("Remember start_browser was: " + start_browser_initial); } put(STARTBROWSER, FALSE); save(); } private void debug(String message) { ///ystem.err.println(message); } private void save() { //debug("Save: " + llssite_cfg.getAbsolutePath()); debug("Save: " + configFile.getAbsolutePath()); try { //BufferedWriter out = new BufferedWriter(new FileWriter(llssite_cfg, false)); BufferedWriter out = new BufferedWriter(new FileWriter(configFile, false)); for(Iterator keys = keySet().iterator(); keys.hasNext(); ) { String key = (String) keys.next(); String value = (String) get(key); out.write(key, 0, key.length()); if(value != null) { out.write('='); // if the server is using llssite.cfg, don't overwrite its default // autoenter and startbrowser values if(configFile == llssite_cfg && (key == AUTOENTER || key == STARTBROWSER)) { if(key == AUTOENTER) { out.write(autoenter_initial, 0, autoenter_initial.length()); } else { // STARTBROWSER out.write(start_browser_initial, 0, start_browser_initial.length()); } } else { out.write(value, 0, value.length()); } } out.newLine(); } out.flush(); out.close(); } catch (Exception error) { error.printStackTrace(); } } private static void copyConfigFile(File source_cfg, File dest_cfg, boolean setToGliSiteDefaults) { // source_cfg file should exist // dest_cfg file should not yet exist // If setToGliSiteDefaults is true, then GLIsite.cfg's default configuration // is applied to concerned lines: autoenter=1, and startbrowser=0 try { BufferedReader in = new BufferedReader(new FileReader(source_cfg)); BufferedWriter out = new BufferedWriter(new FileWriter(dest_cfg, false)); String line = null; while((line = in.readLine()) != null) { if(setToGliSiteDefaults) { if(line.startsWith(AUTOENTER)) { line = AUTOENTER+"=1"; } else if(line.startsWith(STARTBROWSER)) { line = STARTBROWSER+"=0"; } } // write out the line out.write(line + "\n"); } out.flush(); in.close(); out.close(); } catch(Exception e) { System.err.println("Exception occurred when trying to copy the config file " + source_cfg.getName() + " to " + dest_cfg.getName() + ": " + e); e.printStackTrace(); } } } }