package org.greenstone.server; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.net.URL; //import java.net.URLConnection; import java.util.ArrayList; import java.util.Properties; import java.util.StringTokenizer; import org.apache.log4j.PropertyConfigurator; import org.greenstone.util.PortFinder; import org.greenstone.util.ScriptReadWrite; import org.greenstone.util.RunMake; import org.greenstone.util.RunAnt; import org.greenstone.server.BaseServer; import org.greenstone.server.BaseProperty; public class Server2 extends BaseServer { private static final int WAITING_TIME = 10; // time to wait and check for whether the server is running private static final String URL_PENDING="URL_pending"; protected String libraryURL; protected String property_prefix; protected String gsdlos_gsdlarch; private class QuitListener extends Thread { int quitPort = -1; ServerSocket serverSocket = null; public QuitListener(int quitport) throws Exception { ///Server2.this.recordSuccess("In QuitListener constructor"); this.quitPort = quitport; serverSocket = new ServerSocket(quitPort); } public void run() { Socket connection = null; try { // wait for a connection connection = serverSocket.accept(); boolean stop = false; // read input try { BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line = null; while((line = reader.readLine()) != null) { if(line.equals("QUIT")) { stop = true; // Server2.this.recordSuccess("In QuitListener - line is QUIT"); reader.close(); reader = null; serverSocket.close(); serverSocket = null; break; } else if(line.equals("RECONFIGURE")) { server_control_.displayMessage(dictionary.get("ServerControl.Configuring")); reconfigRequired(); } else if(line.equals("RESTART")) { // If the GSI is set to NOT autoenter/autostart the server, then write url=URL_PENDING out to the file. // When the user finally presses the Enter Library button and so has started up the server, the correct // url will be written out to the configfile. String url = property_prefix+"url"; if(config_properties.getProperty(BaseServer.Property.AUTOSTART, "").equals("0")) { if(config_properties.getProperty(url) == null) { config_properties.setProperty(url, URL_PENDING); ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); scriptReadWrite.replaceOrAddLine(fileLines, url, URL_PENDING, true); scriptReadWrite.writeOutFile(config_properties_file, fileLines); } } autoStart(); } } } catch(Exception e) { Server2.this.recordError("Exception in QuitListener thread."); } finally { if(stop) { Server2.this.stop(); System.exit(0); } } } catch(IOException ioe) { Server2.this.recordError("Server2.QuitListener: Unable to make the connection with the client socket." + ioe); } } } public Server2(String gsdl2_home, String gsdlos_arch, String lang, String configfile, int quitPort, String mode) { super(gsdl2_home, lang, configfile, "etc"+File.separator+"logs-gsi"); // configfile can be either glisite.cfg or llssite.cfg //logger_.error("gsdlhome: " + gsdl2_home + " | lang: " + lang + " | configfile: " //+ configfile + " | gsdlos_gsdlarch: " + gsdlos_arch + " | mode: " + property_prefix + " | quitport: " + quitPort); // to get to the OS specific subdirectory of cgi-bin gsdlos_gsdlarch = gsdlos_arch; // property_prefix is the mode we're running in (gli or empty) and contains the prefix // string to look for in config file for auto_enter, start_browser and url properties property_prefix = mode; if(!property_prefix.equals("") && !property_prefix.endsWith(".")) { // ensure a '.' is suffixed if non-empty property_prefix += "."; } Property = new Server2Property(property_prefix); String frame_title = dictionary.get("ServerControl.Frame_Title"); server_control_ = new Server2Control(this,frame_title); /* Make command targets for managing Web server */ START_CMD = "web-start"; RESTART_CMD = "web-restart"; CONFIGURE_CMD = "configure-web \"" + configfile + "\""; STOP_CMD = "web-stop"; // now we can monitor for the quit command if applicable if(quitPort != -1) { // First check if given port is within the range of allowed ports if(PortFinder.isAssignablePortNumber(quitPort)) { try { new QuitListener(quitPort).start(); } catch(Exception e) { Server2.this.recordError("Exception constructing the QuitListener thread."); } } else { recordError("QuitPort provided is not within acceptable range: (" + PortFinder.PORTS_RESERVED + " - " + PortFinder.MAX_PORT + "]" ); quitPort = -1; } } // For some machines, localhost is not sufficient, // need hostname defined as well (e.g. Ubuntu 10.10) InetAddress inetAddress = null; try { inetAddress = InetAddress.getLocalHost(); String hosts = inetAddress.getHostName(); ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); scriptReadWrite.replaceOrAddLine(fileLines, "hosts", hosts, true); scriptReadWrite.writeOutFile(config_properties_file, fileLines); } catch(UnknownHostException e) { // Unable to get hostname, need to try for the default localhost without it } // If the GSI is set to NOT autoenter/autostart the server, then write url=URL_PENDING out to the file. // When the user finally presses the Enter Library button and so has started up the server, the correct // url will be written out to the configfile. if(config_properties.getProperty(BaseServer.Property.AUTOSTART, "").equals("0")) {//if(configfile.endsWith("llssite.cfg")) { String url = property_prefix+"url"; if(config_properties.getProperty(url) == null) { config_properties.setProperty(url, URL_PENDING); ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); scriptReadWrite.replaceOrAddLine(fileLines, url, URL_PENDING, true); scriptReadWrite.writeOutFile(config_properties_file, fileLines); } } autoStart(); } // Prepare the log4j.properties for GS2 protected void initLogger() { String libjavaFolder = gsdl_home+File.separator+"lib"+File.separator+"java"+File.separator; File propsFile = new File(libjavaFolder+"log4j.properties"); // create it from the template file log4j.properties.in if(!propsFile.exists()) { try { // need to set gsdl2.home property's value to be gsdl_home, // so that the location of the log files gets resolved correctly // load the template log4j.properties.in file into logProps FileInputStream infile = new FileInputStream(new File(libjavaFolder+"log4j.properties.in")); if(infile != null) { Properties logProps = new Properties(); logProps.load(infile); infile.close(); // set gsdl3.home to gsdl_home logProps.setProperty("gsdl2.home", gsdl_home); // write the customised properties out to a custom log4j.properties file FileOutputStream outfile = new FileOutputStream(propsFile); if(outfile != null) { logProps.store(outfile, "Customised log4j.properties file"); outfile.close(); } else { System.err.println("Could not store properties file " + propsFile + " for Server2."); } } } catch(Exception e) { System.err.println("Exception occurred when custom-configuring the logger for Server2.\n" + e); } } // now configure the logger with the custom log4j.properties file if(propsFile.exists()) { PropertyConfigurator.configure(propsFile.getAbsolutePath()); } else { System.err.println("Could not create properties file " + propsFile + " for Server2."); } } protected int runTarget(String cmd) { RunMake runMake = new RunMake(); runMake.setTargetCmd(cmd); runMake.run(); return runMake.getTargetState(); } public String getBrowserURL() { return libraryURL; } // works out the library URL again public void reload() { // default values, to be replaced with what's in gsdlsite.cfg String host = "localhost"; String port = "80"; String gwcgi; String httpprefix = "/greenstone"; String suffix = "/cgi-bin/library.cgi"; // get the prefix from the gsdlsite.cfg and build.properties files (port number and servername) try{ File gsdlsite_cfg = new File(gsdl_home + File.separator + "cgi-bin" + File.separator + gsdlos_gsdlarch + File.separator + "gsdlsite.cfg"); FileInputStream fin = new FileInputStream(gsdlsite_cfg); Properties gsdlProperties = new Properties(); if(fin != null) { gsdlProperties.load(fin); gwcgi = gsdlProperties.getProperty("gwcgi"); if(gwcgi != null) { suffix = gwcgi; } else { httpprefix = gsdlProperties.getProperty("httpprefix", httpprefix); suffix = httpprefix + suffix; } fin.close(); } else { recordError("Could not open gsdlsite_cfg for reading, using default library prefix."); } //reloadConfigProperties(); port = config_properties.getProperty("portnumber", port); // The "hosts" property in the config file contains more than one allowed host // Need to work out the particular host chosen from the address_resolution_method // Default is address_resolution_method 2: localhost String addressResolutionMethod = config_properties.getProperty("address_resolution_method"); int address_resolution_method = (addressResolutionMethod == null) ? 2 : Integer.parseInt(addressResolutionMethod); InetAddress inetAddress = null; try { inetAddress = InetAddress.getLocalHost(); } catch(UnknownHostException e) { logger_.error(e); logger_.info("Defaulting host IP to "+ host); // use the default address_resolution_method = 2; inetAddress = null; } switch(address_resolution_method) { case 0: host = inetAddress.getHostName(); break; case 1: host = inetAddress.getHostAddress(); break; case 2: host = "localhost"; break; case 3: host = "127.0.0.1"; break; default: host = "localhost"; } } catch(Exception e) { recordError("Exception trying to load properties from gsdlsite_cfg. Using default library prefix.", e); suffix = httpprefix + suffix; } libraryURL = "http://" + host + ":" + port + suffix; } public boolean reloadConfigProperties(boolean port_has_changed) { super.reloadConfigProperties(port_has_changed); // make sure the port is okay, otherwise find another port // first choice is port 80, second choice starts at 8282 String port = config_properties.getProperty("portnumber", "80"); String keepport = config_properties.getProperty("keepport", "0"); // default is to not try to force the same port if in use by other servers int portDefault = 8282; try { int portNum = Integer.parseInt(port); boolean verbose = true; if(port_has_changed) { // this is the test that prevents the server from arbitrarily shifting the port. // only check at configured port if it's not the current port (the port we // are still running on), because that will always be in use and unavailable. if(!PortFinder.isPortAvailable(portNum, verbose)) { // first time, print any Port Unavailable messages if(keepport.equals("1")) { server_control_.errorMessage(dictionary.get("ServerSettings.SettingsUnchangedPortOccupied", new String[]{port})); String errorMsg = "Unable to run the Greenstone server on port " + port + ". It appears to already be in use."; System.err.println("\n******************"); logger_.error(errorMsg); System.err.println("If you wish to try another port, go to File > Settings of the Greenstone Server interface and either change the port number or untick the \"Do Not Modify Port\" option there. Then press the \"Enter Library\" button."); System.err.println("******************\n"); return false; // property change is unsuccessful } else { // can modify port, try to find a new port PortFinder portFinder = new PortFinder(portDefault, 101); // Search for a free port silently from now on--don't want more // messages saying that a port could not be found... portNum = portFinder.findPortInRange(!verbose); if (portNum == -1) { // If we've still not found a free port, do we try the default port again? System.err.println("No free port found. Going to try on " + portDefault + " anyway."); port = Integer.toString(portDefault); } else { port = Integer.toString(portNum); } config_properties.setProperty("portnumber", port); // store the correct port // write this updated port to the config file, since the configure target uses the file to run ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); scriptReadWrite.replaceOrAddLine(fileLines, "portnumber", port, false); // write the correct port scriptReadWrite.writeOutFile(config_properties_file, fileLines); configure_required_ = true; System.err.println("Running server on port " + port + "."); } } } } catch (Exception e) { recordError("Exception in Server2.reload(): " + e.getMessage()); port = Integer.toString(portDefault); } return true; } // About to stop the webserver // Custom GS2 action: remove the url property from the config file protected void preStop() { ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); // Remove the url=... line, start searching from the end boolean done = false; for (int i = fileLines.size()-1; i >= 0 && !done; i--) { String line = fileLines.get(i).trim(); if(line.startsWith(property_prefix+"url=")) { fileLines.remove(i); done = true; } } scriptReadWrite.writeOutFile(config_properties_file, fileLines); } // Called when the URL has been changed (called after a reload() and starting the server). // By the time we get here, reload() would already have been called and have set // both the port and worked out libraryURL // This method needs to write the URL to the configfile since things should work // like GS2's Local Lib Server for Windows protected void postStart() { URL libURL = null; try { libURL = new URL(libraryURL); } catch (Exception e) { recordError("Unable to convert library URL string into a valid URL, Server2.java." + e); } // 1. Test that the server is running at the libraryURL before writing it out to glisite.cfg/configfile // A quick test involves opening a connection to get the home page for this collection if(libURL != null && !libraryURL.equals(URL_PENDING)) { boolean ready = false; for(int i = 0; i < WAITING_TIME && !ready; i++) { try { libURL.openConnection(); //URLConnection connection = new URL(libraryURL).openConnection(); //connection.getContent(); ready = true; recordSuccess("Try connecting to server on url: '" + libraryURL + "'"); } catch (IOException bad_url_connection) { // keep looping recordSuccess("NOT YET CONNECTED. Waiting to try again..."); try { Thread.sleep(1000); } catch (InterruptedException ie) { ready = true; recordError("Unexpected: got an InterruptedException in sleeping thread, Server2.java." + ie); } } catch (Exception e) { ready = true; recordError("Got an Exception while waiting for the connection to become live, Server2.java." + e); } } } // 2. Now write the URL to the config file String port = config_properties.getProperty("portnumber"); ScriptReadWrite scriptReadWrite = new ScriptReadWrite(); ArrayList fileLines = scriptReadWrite.readInFile(BaseServer.config_properties_file); scriptReadWrite.replaceOrAddLine(fileLines, property_prefix+"url", libraryURL, true); scriptReadWrite.replaceOrAddLine(fileLines, "portnumber", port, false); // write the correct port scriptReadWrite.writeOutFile(config_properties_file, fileLines); } public static void main (String[] args) { if ((args.length < 2) || (args.length > 6)) { System.err.println( "Usage: java org.greenstone.server.Server2 [lang] [--mode=\"gli\"] [--config=configfile] [--quitport=portNum]"); System.exit(1); } String gsdl2_home = args[0]; File gsdl2_dir = new File(gsdl2_home); if (!gsdl2_dir.isDirectory()) { System.err.println("gsdl-home-dir directory does not exist!"); System.exit(1); } String gsdlos_arch = args[1]; //for(int i = 0; i < args.length; i++) { System.err.println("Arg[" + i + "]: |" + args[i] + "|"); } // for every subsequent argument, check whether we're dealing with amalgamated // parameters in that argument. If so, split on whitespace and reconstruct arguments for(int i = 2; i < args.length; i++) { // if the *last* occurrence of a flag parameter is not at the start of // this argument then we're dealing with amalgamated parameters in this // argument. Need to split on whitespace and reconstitute the args list int lastIndex = args[i].lastIndexOf("--"); if(lastIndex > 0) { StringTokenizer tokenizer = new StringTokenizer(args[i]); // splits on whitespace String[] tokens = new String[tokenizer.countTokens()+i]; int j = 0; for(; j < i; j++) { // copy over previous arguments tokens[j] = args[j]; } // the remainder comes from the tokens for(; tokenizer.hasMoreTokens(); j++) { tokens[j] = tokenizer.nextToken(); } args = null; args = tokens; } } // Defaults for optional arguments // if no config file is given, then it defaults to llssite.cfg // (embedded in quotes to preserve spaces in the filepath) File defaultConfigFile = new File(gsdl2_dir, "llssite.cfg"); String configfile = defaultConfigFile.getAbsolutePath(); int port = -1; String mode = ""; String lang = "en"; int index = 2; // move onto any subsequent arguments (past 1st GSDLHOME arg and 2nd GSDLOS_GSDLARCH arg) if (args.length > index && !args[index].startsWith("--")) { lang = args[index]; // 2nd arg is not a flag option, so must be language index++; } // Cycle through arguments, parsing and storing them // note that the batch file could have split arguments in the config- // filepath over several parameters. Here we join them back up again. while(args.length > index) { if(args[index].startsWith("--config=")) { configfile = args[index].substring(args[index].indexOf('=')+1); // get value after '=' sign gsdl2_dir = null; defaultConfigFile = null; } else if(args[index].startsWith("--quitport=")) { String quitport = args[index].substring(args[index].indexOf('=')+1); try { port = Integer.parseInt(quitport); } catch(Exception e) { // parse fails System.err.println("Port must be numeric. Continuing without it."); } } // mode can be: "gli" if launched by GLI or unspecified. If unspecified, then // the gs2-server was launched independently. else if(args[index].startsWith("--mode=")) { mode = args[index].substring(args[index].indexOf('=')+1); } else if(!args[index].startsWith("--")) { // assume it's part of the config file name (that this path had spaces in it) configfile = configfile + " " + args[index]; // reinstate space in path } index++; } configfile = configfile.trim(); // remove any trailing whitespace //System.err.println("\n\n*******\n\ngsdlhome: " + gsdl2_home + " | lang: " + lang + " |configfile:" // + configfile + "| mode: " + mode + " | quitport: " + port + "\n\n*************\n"); new Server2(gsdl2_home, gsdlos_arch, lang, configfile, port, mode); } }