/* Ganymede.java Server main module This class is the main server module, providing the static main() method executed to start the server. This class is never instantiated, but instead provides a bunch of static variables and convenience methods in addition to the main() start method. Created: 17 January 1997 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 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, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.RemoteServer; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.Vector; import org.solinger.cracklib.Packer; import arlut.csd.JDialog.JDialogBuff; import arlut.csd.Util.PackageResources; import arlut.csd.Util.ParseArgs; import arlut.csd.Util.StringUtils; import arlut.csd.Util.TranslationService; import arlut.csd.ganymede.common.BaseListTransport; import arlut.csd.ganymede.common.CategoryTransport; import arlut.csd.ganymede.common.ClientMessage; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.InvidPool; import arlut.csd.ganymede.common.NotLoggedInException; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.common.scheduleHandle; import arlut.csd.ganymede.common.SchemaConstants; import arlut.csd.ganymede.rmi.Server; /*------------------------------------------------------------------------------ class Ganymede ------------------------------------------------------------------------------*/ /** * <p>This class is the main server module, providing the static * main() method executed to start the server. This class is never * instantiated, but instead provides a bunch of static variables and * convenience methods in addition to the main() start method.</p> * * <p>When started, the Ganymede server creates a {@link * arlut.csd.ganymede.server.DBStore DBStore} object, which in turn * creates and loads a set of {@link * arlut.csd.ganymede.server.DBObjectBase DBObjectBase} objects, one * for each type of object held in the ganymede.db file. Each * DBObjectBase contains {@link arlut.csd.ganymede.server.DBObject * DBObject} objects which hold the {@link * arlut.csd.ganymede.server.DBField DBField}'s which ultimately hold * the actual data from the database.</p> * * <p>When the database has been loaded from disk, the main() method * creates a {@link arlut.csd.ganymede.server.GanymedeServer * GanymedeServer} object. GanymedeServer implements the {@link * arlut.csd.ganymede.rmi.Server Server} RMI remote interface, and is * published in the RMI registry.</p> * * <p>Clients and admin consoles may then connect to the published * GanymedeServer object via RMI to establish a connection to the * server.</p> * * <p>The GanymedeServer's {@link * arlut.csd.ganymede.rmi.Server#login(java.lang.String username, * java.lang.String password) login} method is used to create a {@link * arlut.csd.ganymede.server.GanymedeSession GanymedeSession} object * to manage permissions and communications with an individual client. * The client communicates with the GanymedeSession object through the * {@link arlut.csd.ganymede.rmi.Session Session} RMI remote * interface.</p> * * <p>While the GanymedeServer's login method is used to handle client * connections, the GanymedeServer's {@link * arlut.csd.ganymede.rmi.Server#admin(java.lang.String username, * java.lang.String password) admin} method is used to create a {@link * arlut.csd.ganymede.server.GanymedeAdmin GanymedeAdmin} object to * handle the admin console's communications with the server. The * admin console communicates with the GanymedeAdmin object through * the {@link arlut.csd.ganymede.rmi.adminSession adminSession} RMI * remote interface.</p> * * <p>All client permissions and communications are handled by the * higher level {@link arlut.csd.ganymede.server.GanymedeSession} * class, all lower level data manipulation by the {@link * arlut.csd.ganymede.server.DBStore} object and its related classes * ({@link arlut.csd.ganymede.server.DBSession DBSession}, {@link * arlut.csd.ganymede.server.DBObject DBObject}, {@link * arlut.csd.ganymede.server.DBEditSet DBEditSet}, {@link * arlut.csd.ganymede.server.DBNameSpace DBNameSpace}, and {@link * arlut.csd.ganymede.server.DBJournal DBJournal}).</p> * * <p>The ganymede.db file may define a number of task classes that * are to be run by the server at defined times. The server's main() * method starts a background {@link * arlut.csd.ganymede.server.GanymedeScheduler GanymedeScheduler} * thread to schedule the execution of background tasks on additional * threads, including the running of appropriate {@link * arlut.csd.ganymede.server.SyncRunner} and {@link * arlut.csd.ganymede.server.GanymedeBuilderTask} classes registered * for execution in the Ganymede server's database.</p> * * @author Jonathan Abbey, jonabbey@arlut.utexas.edu, ARL:UT */ public final class Ganymede { static public boolean debug = true; /** * <p>TranslationService object for handling string localization in * the Ganymede server.</p> */ static final public TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.Ganymede"); /** * <p>If true, Ganymede.createErrorDialog() will print the * content of error dialogs to the server's stderr.</p> */ static public boolean logErrorDialogs = true; /** * <p>If true, Ganymede.createInfoDialog() will print the * content of info dialogs to the server's stderr.</p> */ static public boolean logInfoDialogs = true; /** * <p>If true, the DBLog class will not send out any email.</p> */ static public boolean suppressEmail = false; /** * <p>If started with this, start Jython server on this port</p> */ static public String portString = null; /** * <p>We keep the server's start time for display in the * admin console.</p> */ static final public Date startTime = new Date(); /** * <p>If the server is started with propFilename on * the command line, the properties will be loaded * from this file.</p> */ static public String propFilename = null; /** * <p>If the server is started with debug=<filename> on * the command line, debugFilename will hold the name * of the file to write our RMI debug log to.</p> */ static public String debugFilename = null; /** * <p>This object is responsible for handling all of the RMI * exportation of objects in the server. According to how it is * initialized, objects may or may not be exported over SSL, and may * or may not be exported on a fixed port.</p> */ static public GanymedeRMIManager rmi = null; /** * <p>Once the server is started and able to accept RMI clients, * this field will hold the GanymedeServer object which clients * talk to in order to login to the server.</p> */ static public GanymedeServer server; /** * <p>A number of operations in the Ganymede server require 'root' * access to the database. This GanymedeSession object is provided * for system database operations.</p> */ static public GanymedeSession internalSession; /** * <p>The background task scheduler.</p> */ static public GanymedeScheduler scheduler; /** * <p>The Jython debug console server, if the telnet option is * specified on the command line.</p> */ static public JythonServer jythonServer; /** * <p>Random access password quality check dictionary.</p> */ static public Packer crackLibPacker; /** * <p>The Ganymede object store.</p> */ static public DBStore db; /** * <p>This object provides access to the Ganymede log file, * providing transaction logging, email, and search services.</p> */ static public DBLog log = null; /** * <p>A cached reference to a master category tree serialization * object. Initialized the first time a user logs on to the server, * and re-initialized when the schema is edited. This object is * provided to clients when they call {@link * arlut.csd.ganymede.server.GanymedeSession#getCategoryTree() * GanymedeSession.getCategoryTree()}.</p> */ static public CategoryTransport catTransport = null; /** * <p>A cached reference to a master base list serialization object. * Initialized on server start up and re-initialized when the schema * is edited. This object is provided to clients when they call * {@link arlut.csd.ganymede.server.GanymedeSession#getBaseList() * GanymedeSession.getBaseList()}.</p> */ static public BaseListTransport baseTransport = null; /** * <p>The Thread that we have registered to handle cleanup if we get a * kill/quit signal.</p> * * <p>This is public so that the GanymedeServer class can de-register * this thread at shutdown time to avoid recursion on exit().</p> */ static public Thread signalHandlingThread = null; // properties from the ganymede.properties file with their default // values static public String dbFilename = null; static public String journalProperty = null; static private String logProperty = null; static private String mailLogProperty = null; static public String serverHostProperty = null; static public String rootname = null; static public String defaultrootpassProperty = null; static public String mailHostProperty = null; static public String defaultDomainProperty = null; static public String returnaddrProperty = null; static public String returnaddrdescProperty = null; static public String subjectPrefixProperty = null; static public String signatureFileProperty = null; static public String helpbaseProperty = null; static public String monitornameProperty = null; static public String defaultmonitorpassProperty = null; static public String messageDirectoryProperty = null; static private String schemaDirectoryProperty = null; static public int registryPortProperty = 1099; static private int publishedObjectPortProperty = 55555; static public String logHelperProperty = null; static public boolean softtimeout = false; static public int timeoutIdleNoObjs = 15; static public int timeoutIdleWithObjs = 20; static private boolean cracklibEnabled = false; static private String cracklibDirectoryProperty = null; /** * <p>If the ganymede.bugaddress property is set, that string will * be copied into this variable. It should be an email address to * send bug traces encountered by the Ganymede system.</p> * * <p>This system is used primarily by the client at this writing, * through the {@link * arlut.csd.ganymede.server.GanymedeSession#reportClientBug(java.lang.String, * java.lang.String)} method.</p> * * <p>If this property string is null or empty, client bugs will not * be emailed, but they will be written to the server's standard * error stream.</p> */ static public String bugReportAddressProperty = null; /** * <p>If the Ganymede server is started with the -magic_import command * line flag, this field will be set to true and the server will * allow invids, creation timestamps, creator info, last * modification timestamps and last modification information to be * injected into objects loaded from the xmlclient.</p> */ static public boolean allowMagicImport = false; /** * <p>If the server is started with the -resetadmin command line flag, * this field will be set to true and the server's startupHook() will * reset the supergash password to that specified in the server's * ganymede.properties file.</p> */ static public boolean resetadmin = false; /** * <p>This flag is true if the server was started with no * pre-existing ganymede.db file. If true, the {@link * arlut.csd.ganymede.server.GanymedeSession GanymedeSession} class * will not worry about not finding the default permissions role in * the database.</p> */ static public boolean firstrun = false; /** * <p>This flag is true if the server was started with a -forcelocalhost * command line argument, which will allow the server to run even if * it will only be accessible to localhost.</p> */ static public boolean forcelocalhost = false; /** * <p>This flag is set true unless the server was started with a * -nossl command line argument. Unless -nossl is provided, the * server will force all client-server communications to be * encrypted using SSL and the certificate material generated by * the Ganymede build process's genkeys ant target.</p> */ static private boolean useSSL = true; /** * <p>Default localized "Ok" string, usable by server-side Ganymede * code which prepares dialogs. </p> */ public final static String OK = ts.l("global.ok"); /** * <p>Default localized "Cancel" string, usable by server-side * Ganymede code which prepares dialogs. </p> */ public final static String CANCEL = ts.l("global.cancel"); /** * <p>This upgradeClassMap is used to rewrite the standard task * class names if we're loading an older (pre-DBStore 2.7 format) * ganymede.db file at task registration time.</p> */ static private HashMap<String, String> upgradeClassMap = null; /** * <p>Uncaught exception handler in use on the Ganymede server. * Used to log exceptions and send details about them to the email * addresses in the bugReportAddressProperty, if defined.</p> */ static private GanymedeUncaughtExceptionHandler defaultHandler = null; /* -- */ /** * <p>The Ganymede server start point.</p> * * @param argv - command line arguments. */ static public void main(String argv[]) { File dataFile; /* -- */ System.setProperty("java.awt.headless", "true"); setupUncaughtExceptionHandler(); try { checkArgs(argv); loadProperties(propFilename); // If we are going to use CrackLib, make sure our password // dictionary exists, creating it if necessary. initializeCrackLib(); // figure out what IP address / hostname we're going to use String hostname = getServerHost(); // make sure we use the same IP address for our skel proxy // generation as we are doing for our RMI binding // // this is necesssary in the case where we are deliberately // binding to 127.0.0.1 due to network routing / firewall // issues, and we don't want the skel classes to try to use // the host's default IP address System.setProperty("java.rmi.server.hostname", hostname); // Create our GanymedeRMIManager to handle RMI exports. We need // to create this before creating our DBStore, as some of the // components of DBStore are to be made accessible through RMI startRMIManager(); // "Creating DBStore structures" debug(ts.l("main.info_creating_dbstore")); db = new DBStore(); // And how can this be!? For he IS the kwizatch-haderach!! // Load the database dataFile = new File(dbFilename); if (dataFile.exists()) { // "Loading DBStore contents" debug(ts.l("main.info_loading_dbstore")); db.load(dbFilename); } else { // No database on disk.. create a new one, along with a new journal createNewDB(); } createGanymedeServer(); createGanymedeSession(); startTransactionLog(); startScheduler(); // Take care of any startup-time database modifications startupHook(); bindGanymedeRMI(hostname); // At this point clients can log in to the server.. the RMI system // will spawn threads as necessary to handle RMI client activity.. // the main thread has nothing left to do and can go ahead and // terminate here. // "Setup and bound server object OK" debug(ts.l("main.info_setup_okay")); startJythonServer(); registerKillHandler(); // "Ganymede Server Ready." debug("\n--------------------------------------------------------------------------------"); debug(ts.l("main.info_ready")); debug("--------------------------------------------------------------------------------\n"); } catch (GanymedeStartupException ex) { ex.process(); } } /** * <p>Checks any command line arguments passed in.</p> * * @param argv - command line arguments. * * @throws GanymedeStartupException if a required parameter is missing. */ static private void checkArgs(String argv[]) throws GanymedeStartupException { String useDirectory = null; // If the "usedirectory" option is set, then use the supplied // directory name as the base path to the properties file (which // is assumed to be "ganymede.properties" and the debug log // (assumed to be debug.log). useDirectory = ParseArgs.getArg("usedirectory", argv); if (useDirectory != null) { String fileSeparator = System.getProperty("file.separator"); propFilename = useDirectory + fileSeparator + "ganymede.properties"; if (ParseArgs.switchExists("logrmi", argv)) { debugFilename = useDirectory + fileSeparator + "debug.log"; } } if (ParseArgs.getArg("properties", argv) != null) { propFilename = ParseArgs.getArg("properties", argv); } if (ParseArgs.getArg("logrmi", argv) != null) { debugFilename = ParseArgs.getArg("logrmi", argv); } if (propFilename == null) { // "Error: invalid command line parameters" System.err.println(ts.l("main.cmd_line_error")); // "Usage: java arlut.csd.ganymede.server.Ganymede\n properties = [-usedirectory=<server directory>|<property file>] [-resetadmin] [debug = <rmi debug file>]" System.err.println(ts.l("main.cmd_line_usage")); throw new GanymedeSilentStartupException(); } if (ParseArgs.switchExists("nossl", argv)) { useSSL = false; // "***\n*** SSL disabled by use of -nossl command line switch ***\n***" System.err.println(ts.l("main.nossl")); } else { // "SSL enabled" System.err.println(ts.l("main.ssl")); } resetadmin = ParseArgs.switchExists("resetadmin", argv); allowMagicImport = ParseArgs.switchExists("magic_import", argv); forcelocalhost = ParseArgs.switchExists("forcelocalhost", argv); suppressEmail = ParseArgs.switchExists("suppressEmail", argv); portString = ParseArgs.getArg("telnet", argv); return; } /** * <p>This method loads properties from the ganymede.properties * file.</p> * * @throws GanymedeStartupException If all necessary properties could * not be loaded. */ static public void loadProperties(String filename) throws GanymedeStartupException { Properties props = new Properties(System.getProperties()); FileInputStream fis = null; BufferedInputStream bis = null; /* -- */ // "Ganymede server: loading properties from {0}" System.out.println(ts.l("loadProperties.propload", filename)); try { fis = new FileInputStream(filename); bis = new BufferedInputStream(fis); props.load(bis); } catch (IOException ex) { throw new GanymedeStartupException(ts.l("loadProperties.nopropfile", filename)); } finally { // don't wait for GC to close the file descriptors try { if (bis != null) { bis.close(); } } catch (IOException ex) { } try { if (fis != null) { fis.close(); } } catch (IOException ex) { } } // make the combined properties file accessible throughout our server code. System.setProperties(props); dbFilename = System.getProperty("ganymede.database"); journalProperty = System.getProperty("ganymede.journal"); logProperty = System.getProperty("ganymede.log"); mailLogProperty = System.getProperty("ganymede.maillog"); serverHostProperty = System.getProperty("ganymede.serverhost"); rootname = System.getProperty("ganymede.rootname"); defaultrootpassProperty = System.getProperty("ganymede.defaultrootpass"); mailHostProperty = System.getProperty("ganymede.mailhost"); defaultDomainProperty = System.getProperty("ganymede.defaultdomain"); signatureFileProperty = System.getProperty("ganymede.signaturefile"); returnaddrProperty = System.getProperty("ganymede.returnaddr"); returnaddrdescProperty = System.getProperty("ganymede.returnaddrdesc"); subjectPrefixProperty = System.getProperty("ganymede.subjectprefix"); helpbaseProperty = System.getProperty("ganymede.helpbase"); monitornameProperty = System.getProperty("ganymede.monitorname"); defaultmonitorpassProperty = System.getProperty("ganymede.defaultmonitorpass"); messageDirectoryProperty = System.getProperty("ganymede.messageDirectory"); schemaDirectoryProperty = System.getProperty("ganymede.schemaDirectory"); logHelperProperty = System.getProperty("ganymede.loghelper"); bugReportAddressProperty = System.getProperty("ganymede.bugsaddress"); String cracklibEnabledString = System.getProperty("ganymede.usecracklib"); if (cracklibEnabledString != null && cracklibEnabledString.equalsIgnoreCase("true")) { cracklibDirectoryProperty = System.getProperty("ganymede.cracklibDirectory"); if (cracklibDirectoryProperty == null) { // "No ganymede.cracklibDirectory property specified, can''t enable cracklib processing." throw new GanymedeStartupException(ts.l("loadProperties.no_cracklib_dir")); } else { File cracklibDir = new File(cracklibDirectoryProperty); if (!cracklibDir.isDirectory() || !cracklibDir.canWrite() || !cracklibDir.canRead()) { // "No usable directory matching the ganymede.cracklibDirectory property ({0}) exists, // can''t enable cracklib processing." throw new GanymedeStartupException(ts.l("loadProperties.bad_cracklib_dir", cracklibDirectoryProperty)); } else { cracklibEnabled = true; } } } String softtimeoutString = System.getProperty("ganymede.softtimeout"); if (softtimeoutString != null && softtimeoutString.equalsIgnoreCase("true")) { softtimeout = true; } String timeoutIdleNoObjsString = System.getProperty("ganymede.timeoutIdleNoObjs"); if (timeoutIdleNoObjsString != null) { try { timeoutIdleNoObjs = java.lang.Integer.parseInt(timeoutIdleNoObjsString); } catch (NumberFormatException ex) { throw new GanymedeStartupException(ts.l("loadProperties.no_parse_timeoutIdleNoObjs", timeoutIdleNoObjsString)); } } String timeoutIdleWithObjsString = System.getProperty("ganymede.timeoutIdleWithObjs"); if (timeoutIdleWithObjsString != null) { try { timeoutIdleWithObjs = java.lang.Integer.parseInt(timeoutIdleNoObjsString); } catch (NumberFormatException ex) { throw new GanymedeStartupException(ts.l("loadProperties.no_parse_timeoutIdleWithObjs", timeoutIdleWithObjsString)); } } if (dbFilename == null) { // "Couldn''t get the ganymede.database property" throw new GanymedeStartupException(ts.l("loadProperties.no_db")); } if (journalProperty == null) { // "Couldn''t get the ganymede.journal property" throw new GanymedeStartupException(ts.l("loadProperties.no_journal")); } if (logProperty == null) { // "Couldn''t get the ganymede.log property" throw new GanymedeStartupException(ts.l("loadProperties.no_log")); } if (serverHostProperty == null) { // "Couldn''t get the ganymede.serverhost property" throw new GanymedeStartupException(ts.l("loadProperties.no_server_host")); } if (rootname == null) { // "Couldn''t get the ganymede.rootname property" throw new GanymedeStartupException(ts.l("loadProperties.no_root_name")); } // we don't care if we don't get the mailHostProperty, since // we don't require that the server be able to send out mail. if (mailHostProperty == null || mailHostProperty.equals("")) { // "***\n*** Email Sending disabled by use of -suppressEmail command line switch or by lack of ganymede.mailhost property ***\n***" System.err.println(ts.l("loadProperties.no_mail_host")); mailHostProperty = null; } if (defaultDomainProperty == null || defaultDomainProperty.equals("")) { // "No ganymede.defaultdomain property set, won''t be able to normalize user email addresses when sending change mail." System.err.println(ts.l("loadProperties.no_default_domain")); defaultDomainProperty = null; } if (returnaddrProperty == null) { // "Couldn''t get the ganymede.returnaddr return email address property" throw new GanymedeStartupException(ts.l("loadProperties.no_email_addr")); } if (returnaddrdescProperty == null) { returnaddrdescProperty = returnaddrProperty; } // if the subjectPrefixProperty is not defined or if it does not begin and // end with single quote marks, use a default prefix if ((subjectPrefixProperty == null) || (subjectPrefixProperty.charAt(0) != '\'' || subjectPrefixProperty.charAt(subjectPrefixProperty.length()-1) != '\'')) { subjectPrefixProperty = "Ganymede: "; } else { subjectPrefixProperty = subjectPrefixProperty.substring(1, subjectPrefixProperty.length()-1); } if (signatureFileProperty == null) { // "Couldn''t get the ganymede.signaturefile property" throw new GanymedeStartupException(ts.l("loadProperties.no_sig")); } if (helpbaseProperty == null || helpbaseProperty.equals("")) { // "Couldn''t get the ganymede.helpbase property.. setting to null" throw new GanymedeStartupException(ts.l("loadProperties.no_help_base")); } if (monitornameProperty == null) { // "Couldn''t get the ganymede.monitorname property." throw new GanymedeStartupException(ts.l("loadProperties.no_monitor_name")); } if (defaultmonitorpassProperty == null) { // not a failure condition // "Couldn''t get the ganymede.defaultmonitorpassw property.. may have problems if initializing a new db" System.err.println(ts.l("loadProperties.no_monitor_pass")); } // get the registry port number String registryPort = System.getProperty("ganymede.registryPort"); if (registryPort != null) { try { registryPortProperty = java.lang.Integer.parseInt(registryPort); } catch (NumberFormatException ex) { // "Couldn''t get a valid registry port number from the ganymede.registryPort property: {0}" throw new GanymedeStartupException(ts.l("loadProperties.no_registry_port", registryPort)); } } // get the published object port number String publishedObjectPort = System.getProperty("ganymede.publishedObjectPort"); if (publishedObjectPort != null) { try { publishedObjectPortProperty = java.lang.Integer.parseInt(publishedObjectPort); } catch (NumberFormatException ex) { // "Couldn''t get a valid published object port number from ganymede.publishedObjectPort property: {0}" throw new GanymedeStartupException(ts.l("loadProperties.no_object_port", publishedObjectPort)); } } // if the main ganymede.properties file has a // schemaDirectory property, load in the properties from // the schema's properties file. if (schemaDirectoryProperty != null && !schemaDirectoryProperty.equals("")) { try { String propName = arlut.csd.Util.PathComplete.completePath(schemaDirectoryProperty) + "schema.properties"; // "Attempting to read schema properties: {0}" System.err.println(ts.l("loadProperties.reading_schema_props", propName)); fis = new FileInputStream(propName); bis = new BufferedInputStream(fis); props.load(bis); } catch (IOException ex) { } finally { // don't wait for GC to close the file descriptors try { if (bis != null) { bis.close(); } } catch (IOException ex) { } try { if (fis != null) { fis.close(); } } catch (IOException ex) { } } } } /** * <p>Make sure that we have our random access crack lib dictionary * available to us. </p> */ static private void initializeCrackLib() { if (!cracklibEnabled) { // "CrackLib disabled, no internal password quality checking available." System.err.println(ts.l("main.cracklibDisabled")); return; } // "CrackLib enabled for password quality checking." System.err.println(ts.l("main.cracklibEnabled")); try { File crackData = new File(cracklibDirectoryProperty, "cracklib_dict.pwd"); File crackIndex = new File(cracklibDirectoryProperty, "cracklib_dict.pwi"); File crackHash = new File(cracklibDirectoryProperty, "cracklib_dict.hwm"); String pathPrefix = cracklibDirectoryProperty + File.separator + "cracklib_dict"; if (crackData.isFile() && crackData.canRead() && crackIndex.isFile() && crackIndex.canRead() && crackHash.isFile() && crackHash.canRead()) { // "Loading crack lib dictionary from {0}." System.err.println(ts.l("initializeCrackLib.loading_dictionary", pathPrefix)); crackLibPacker = new Packer(pathPrefix, "r"); } else { // "Creating random access crack lib dictionary {0}." System.err.println(ts.l("initializeCrackLib.creating_dictionary", pathPrefix)); Packer.make(PackageResources.getPackageResourceAsStream("words", org.solinger.cracklib.CrackLib.class), pathPrefix, false); // "Loading crack lib dictionary from {0}." System.err.println(ts.l("initializeCrackLib.loading_dictionary", pathPrefix)); crackLibPacker = new Packer(pathPrefix, "r"); } // "Loaded {0} words from crack lib dictionary. System.err.println(ts.l("initializeCrackLib.loaded_dictionary", crackLibPacker.size())); } catch (IOException ex) { ex.printStackTrace(); // "CrackLib disabled, no internal password quality checking available." System.err.println(ts.l("main.cracklibDisabled")); } } /** * <p>Create our GanymedeRMIManager to handle RMI exports. * and start it up. </p> */ static private void startRMIManager() { // Create our GanymedeRMIManager to handle RMI exports. We need // to create this before creating our DBStore, as some of the // components of DBStore are to be made accessible through RMI rmi = new GanymedeRMIManager(publishedObjectPortProperty, useSSL); // Start up the RMI registry thread. // "Creating RMI registry on port {0,number,###}" debug(ts.l("main.info_starting_registry", registryPortProperty)); try { rmi.startRMIRegistry(registryPortProperty); } catch (RemoteException ex) { // "Error, couln''t start the RMI registry." System.err.println(ts.l("main.error_starting_rmiregistry")); throw new RuntimeException(ex.getMessage()); } // if debug=<filename> was specified on the command line, tell the // RMI system to log RMI calls and exceptions that occur in // response to RMI calls. if (debugFilename != null) { // RMI Logging to {0} System.err.println(ts.l("main.info_rmilogging", debugFilename)); try { RemoteServer.setLog(new FileOutputStream(debugFilename)); } catch (IOException ex) { // "Couldn''t open RMI debug log: {0}" System.err.println(ts.l("main.error_fail_debug", ex.toString())); } } else { // Make RMI log any exceptions thrown in response to client calls // to stderr.. XXX not sure this should always be done if the debug // file is not specified on the command line XXX System.getProperties().setProperty("sun.rmi.server.exceptionTrace", "true"); } } /** * <p>Sets up our default UncaughtExceptionHandler.</p> * * <p>This method introduces a dependency on Java 1.5 or later.</p> */ static private void setupUncaughtExceptionHandler() { defaultHandler = new GanymedeUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(defaultHandler); } /** * <p>No database exists on disk.. create a new journal file * but first, let's make sure there is no journal left alone * without a db file. Does not write out a db file here.</p> */ static public void createNewDB() throws GanymedeStartupException { File journalFile = new File(journalProperty); if (journalFile.exists()) { // *** // *** Error, I found an orphan journal ({0}), but no matching database file ({1}) to go with it. // *** // *** You need either to restore the {1} file, or to remove the {0} file. // *** throw new GanymedeSilentStartupException(ts.l("main.orphan_journal", journalProperty, dbFilename)); } firstrun = true; Invid.setAllocator(new InvidPool()); // "No DBStore exists under filename {0}, not loading" debug(ts.l("main.info_new_dbstore", dbFilename)); // "Initializing new schema" debug(ts.l("main.info_initializing_schema")); db.initializeSchema(); // "Template schema created." debug(ts.l("main.info_created_schema")); try { db.journal = new DBJournal(db, journalProperty); } catch (IOException ex) { // what do we really want to do here? // "couldn''t initialize journal" throw new RuntimeException(ts.l("main.error_no_init_journal")); } // create the database objects required for the server's operation // "Creating mandatory database objects" debug(ts.l("main.info_creating_mandatory")); db.initializeObjects(); // "Mandatory database objects created." debug(ts.l("main.info_created_mandatory")); firstrun = false; } /** * <p>Create a GanymedeServer object to support the logging * code... the GanymedeServer's main purpose (to allow logins) * won't come into play until we bind the server object into the * RMI registry.</p> */ static private void createGanymedeServer() throws GanymedeStartupException { try { // "Creating GanymedeServer object" debug(ts.l("createGanymedeServer.info_creating_server")); server = new GanymedeServer(); } catch (Exception ex) { // "Couldn''t create GanymedeServer: " throw new GanymedeStartupException(ts.l("createGanymedeServer.error_fail_server") + stackTrace(ex)); } } /** * <p>Create the internal GanymedeSession that we use for system * database maintenance and general operations.</p> */ static private void createGanymedeSession() throws GanymedeStartupException { try { // "Creating internal Ganymede Session" debug(ts.l("createGanymedeSession.info_creating_def_session")); internalSession = new GanymedeSession(); internalSession.enableWizards(false); internalSession.enableOversight(false); // "Creating master BaseListTransport object" debug(ts.l("createGanymedeSession.info_creating_baselist_trans")); baseTransport = internalSession.getBaseList(); } catch (Exception ex) { throw new GanymedeStartupException(ts.l("createGanymedeSession.error_fail_session") + ex); } } /** * Creates the DBLog and sets options on it. */ static private void startTransactionLog() throws GanymedeStartupException { // First, we check to see if there is a designated mail host. If // not, then we automatically turn off the sending of emails. // // Likewise, suppressEmail is the email command line "master // switch". If it's set to true, then we won't actually send any // emails out. if (mailHostProperty == null || mailHostProperty.equals("")) { suppressEmail = true; } try { if (mailLogProperty != null && !mailLogProperty.equals("")) { log = new DBLog(new DBLogFileController(logProperty), new DBLogFileController(mailLogProperty), internalSession, suppressEmail); } else { log = new DBLog(new DBLogFileController(logProperty), null, internalSession, suppressEmail); } } catch (IOException ex) { ex.printStackTrace(); // "Couldn''t initialize log file" throw new GanymedeStartupException(ts.l("main.error_log_file")); } // log our restart String startMesg; if (debugFilename != null) { // "Server startup - Debug mode" startMesg = ts.l("main.info_debug_start"); } else { // "Server startup - Not in Debug mode" startMesg = ts.l("main.info_nodebug_start"); } log.logSystemEvent(new DBLogEvent("restart", startMesg, null, null, null, null)); } /** * <p>Creates a background scheduler, and registers the tasks and * sync channels for it.</p> */ static private void startScheduler() throws GanymedeStartupException { // Create the background scheduler scheduler = new GanymedeScheduler(true); // set the background scheduler running on its own thread scheduler.start(); // and install the tasks and sync channels listed in the database try { registerTasks(); registerSyncChannels(); // there is a very, very small chance that an abnormal // termination might have left some xml bits in the sync // channels for a transaction that ultimately could not be // processed. We check for that here, and clean up any // remaining bits if we find them. DBJournalTransaction incompleteTransaction = db.journal.getIncompleteTransaction(); if (incompleteTransaction != null) { for (scheduleHandle handle: scheduler.getTasksByClass(SyncRunner.class)) { SyncRunner channel = (SyncRunner) handle.task; try { channel.unSync(incompleteTransaction); } catch (IOException ex) { // what can we do? keep clearing them out as best we // can ex.printStackTrace(); } } db.journal.clearIncompleteTransaction(); } } catch (NotLoggedInException ex) { // "Mysterious not logged in exception: " throw new GanymedeStartupException(ts.l("main.error_myst_nologged") + ex.getMessage()); } } /** * <p>This method is provided to allow us to hook in creation of new * objects with specified invid's that the server code references.</p> * * <p>It's intended for use during server development as we evolve * the schema.</p> */ static private void startupHook() { // check to make sure the datastructures for supergash in the // db file are set. db.initializeObjects(); try { // and reset the password if we need to. if (resetadmin && defaultrootpassProperty != null && !defaultrootpassProperty.trim().equals("")) { resetAdminPassword(); } // At DBStore 2.11, we added a hidden label field for objectEvent // objects. We'll edit any old ones here and fix up their labels if (db.isLessThan(2,11)) { AddLabelUpdate(); } } catch (NotLoggedInException ex) { // "Mysterious not logged in exception: " throw new Error(ts.l("main.error_myst_nologged") + ex.getMessage()); } } static public String getServerHost() throws GanymedeStartupException { String hostname = null; try { if (serverHostProperty != null && !serverHostProperty.equals("")) { hostname = java.net.InetAddress.getByName(serverHostProperty).getHostAddress(); } else { hostname = java.net.InetAddress.getLocalHost().getHostAddress(); } if (hostname.equals("127.0.0.1")) { // we don't want to bind to a system name that will // resolve to 127.0.0.1, or else otherwise the rmiregistry // will attempt to report our address as loopback, which // won't do anyone any good if (forcelocalhost) { debug("\n** " + ts.l("main.warning") + " **\n"); } else { debug("\n** " + ts.l("main.error") + " **\n"); } // "The system hostname ({0}) and/or the // ganymede.serverhost definition ({1}) resolve to the // 127.0.0.1 loopback address" debug(ts.l("main.error_loopback", java.net.InetAddress.getLocalHost().getHostName(), serverHostProperty)); // "The Ganymede server must have an externally // accessible IP address or else clients // will not be able to communicate with the // Ganymede server from other than localhost." debug(ts.l("main.error_loopback_explain")); if (!forcelocalhost) { // "If you really want to be only usable for // localhost, edit the runServer script to use // the -forcelocalhost option" debug(ts.l("main.error_loopback_explain2")); // "Shutting down." debug("\n" + ts.l("main.info_shutting_down") + "\n"); throw new GanymedeSilentStartupException(); } } } catch (Throwable ex) { throw new GanymedeStartupException(ex); } return hostname; } /** * <p>Bind the GanymedeServer object in the RMI registry so clients * and admin consoles can connect to us.</p> */ static public void bindGanymedeRMI(String hostname) throws GanymedeStartupException { String bindingName = null; try { // tell the RMI registry where to find the server bindingName = "rmi://" + hostname + ":" + registryPortProperty + "/ganymede.server"; // "Binding GanymedeServer in RMI Registry as {0}" debug(ts.l("main.info_binding_hostname", bindingName)); Naming.bind(bindingName, server); } catch (RuntimeException ex) { // We're catching RuntimeException separately to placate // FindBugs. // "Couldn''t establish server binding {0}: " throw new GanymedeStartupException(ts.l("main.error_no_binding", bindingName) + ex, ex); } catch (Exception ex) { // "Couldn''t establish server binding {0}: " throw new GanymedeStartupException(ts.l("main.error_no_binding", bindingName) + ex, ex); } } /** * <p> If we've been given a telnet port on the command line, set up a * Jython console interpreter that implementers can telnet to for debug.</p> */ static private void startJythonServer() { if (portString != null) { try { int portNumber = Integer.parseInt(portString); jythonServer = new JythonServer(); jythonServer.run(portNumber); } catch (NumberFormatException ex) { // "Could not start telnet console, {0} is not a valid port number." debug(ts.l("main.badport", portString)); } } } /** * <p>Register a thread to respond if the server hits ctrl-C on the * server's stdin or sends a SIGQUIT signal, etc.</p> */ static private void registerKillHandler() { signalHandlingThread = new Thread(new Runnable() { public void run() { try { GanymedeServer.shutdown("SIGQUIT received", null); } finally { java.lang.Runtime.getRuntime().halt(1); } } }, ts.l("main.signalCatchThread")); // "Ganymede ctrl-C handling thread" java.lang.Runtime.getRuntime().addShutdownHook(signalHandlingThread); } /** * <p>This is a convenience method used by server-side code to send * debug output to stderr and to any attached admin consoles.</p> */ static public void debug(String string) { if (debug) { System.err.println(string); } GanymedeAdmin.logAppend(string); } /** * <p>This is a convenience method used by server-side code to send * error logging output to stderr, to any attached admin consoles, * and to the registered bugReportAddressProperty.</p> */ static public void logError(Throwable ex) { if (defaultHandler != null) { defaultHandler.reportException(ex); } else { ex.printStackTrace(); } } /** * <p>This is a convenience method used by server-side code to send * error logging output to stderr, to any attached admin consoles, * and to the registered bugReportAddressProperty. </p> */ static public void logError(Throwable ex, String contextMsg) { if (defaultHandler != null) { defaultHandler.reportException(contextMsg, ex); } else { System.err.println(contextMsg); ex.printStackTrace(); } } /** * <p>This is a convenience method used by the server to get a * stack trace from a throwable object in String form. </p> */ static public String stackTrace(Throwable thing) { StringWriter stringTarget = new StringWriter(); PrintWriter writer = new PrintWriter(stringTarget); thing.printStackTrace(writer); writer.close(); return stringTarget.toString(); } /** * <p>This is a convenience method used by the server to generate a * stack trace print from a server code. </p> */ static public void printCallStack() { try { throw new RuntimeException("TRACE"); } catch (RuntimeException ex) { ex.printStackTrace(); } } /** * <p>This is a convenience method to allow arbitrary Ganymede code * to generate a stack trace on the server's log and admin * consoles.</p> */ static public void logAssert(String string) { try { throw new RuntimeException("ASSERT: " + string); } catch (RuntimeException ex) { debug(stackTrace(ex)); } } /** * <p>This is a convenience method used by the server to return a * standard informative dialog.</p> */ static public ReturnVal createInfoDialog(String title, String body) { return Ganymede.createInfoDialog(null, title, body); } /** * <p>This is a convenience method used by the server to return a * standard informative dialog.</p> */ static public ReturnVal createInfoDialog(GanymedeSession session, String title, String body) { ReturnVal retVal = new ReturnVal(true,true); // success ok, doNormalProcessing ok retVal.setDialog(new JDialogBuff(title, body, OK, null, "ok.gif")); if (logInfoDialogs) { if (session == null) { // "[INFO]: {0}" System.err.println(ts.l("createInfoDialog.log_info", body)); } else { String username = session.getIdentity(); String hostname = session.getClientHostName(); if (hostname != null && !hostname.trim().equals("")) { // "[INFO] {0} on {1}: " System.err.print(StringUtils.insertPrefixPerLine(body, ts.l("createInfoDialog.user_host_prefix", username, hostname))); } else { // "[INFO] {0}: " System.err.print(StringUtils.insertPrefixPerLine(body, ts.l("createInfoDialog.user_prefix", username))); } } } return retVal; } /** * <p>This is a convenience method used by the server to return a * standard error dialog without a custom title. This is primarily * useful for returning errors from {@link * arlut.csd.ganymede.server.GanymedeXMLSession} in which the errors * will be reported through a purely textual interface, but may be * used anywhere where a default title is acceptable.</p> */ static public ReturnVal createErrorDialog(String body) { return Ganymede.createErrorDialog(null, null, body); } /** * <p>This is a convenience method used by the server to return a * standard error dialog.</p> */ static public ReturnVal createErrorDialog(String title, String body) { return Ganymede.createErrorDialog(null, title, body); } /** * <p>This is a convenience method used by the server to return a * standard error dialog.</p> */ static public ReturnVal createErrorDialog(GanymedeSession session, String title, String body) { String myTitle = title; if (myTitle == null) { myTitle = ts.l("createErrorDialog.default_title"); // "Error" } ReturnVal retVal = new ReturnVal(false); retVal.setDialog(new JDialogBuff(myTitle, body, OK, null, "error.gif")); if (logErrorDialogs) { if (session == null) { // "[ERR]: {0}" System.err.println(ts.l("createErrorDialog.log_error", body)); } else { String username = session.getIdentity(); String hostname = session.getClientHostName(); if (hostname != null && !hostname.trim().equals("")) { // "[ERR] {0} on {1}: " System.err.print(StringUtils.insertPrefixPerLine(body, ts.l("createErrorDialog.user_host_prefix", username, hostname))); } else { // "[ERR] {0}: " System.err.print(StringUtils.insertPrefixPerLine(body, ts.l("createErrorDialog.user_prefix", username))); } } } return retVal; } /** * <p>This is a convenience method used by the server to return a * very standard error dialog.</p> * * <p>The Exception parameter is ignored for now, so that this method * can do something with it later if necessary without having to go * through all the code which calls this method.</p> */ static public ReturnVal loginError(Exception ex) { return createErrorDialog(ts.l("loginError.error"), ts.l("loginError.explain")); } /** * <p>Reset the password to match our properties file because we have a * -resetadmin command line argument. </p> */ static private void resetAdminPassword() throws NotLoggedInException { Invid supergashinvid = Invid.createInvid(SchemaConstants.PersonaBase, SchemaConstants.PersonaSupergashObj); DBObject v_object; DBEditObject e_object; PasswordDBField p; /* -- */ v_object = DBStore.viewDBObject(supergashinvid); p = v_object.getPassField(SchemaConstants.PersonaPasswordField); if (p == null || !p.matchPlainText(defaultrootpassProperty)) { // "Resetting supergash password." System.err.println(ts.l("startupHook.resetting")); internalSession.openTransaction("Ganymede startupHook"); e_object = (DBEditObject) internalSession.getDBSession().editDBObject(supergashinvid); if (e_object == null) { throw new RuntimeException(ts.l("startupHook.no_supergash", rootname)); } p = e_object.getPassField(SchemaConstants.PersonaPasswordField); ReturnVal retVal = p.setPlainTextPass(defaultrootpassProperty); // default supergash password if (!ReturnVal.didSucceed(retVal)) { throw new RuntimeException(ts.l("startupHook.failed_reset", rootname)); } System.out.println(ts.l("startupHook.password_reset", rootname)); retVal = internalSession.commitTransaction(); if (!ReturnVal.didSucceed(retVal)) { // if doNormalProcessing is true, the // transaction was not cleared, but was // left open for a re-try. Abort it. if (retVal.doNormalProcessing) { internalSession.abortTransaction(); } } } } /** * <p> At DBStore 2.11, we added a hidden label field for objectEvent * objects. We'll edit any old ones here and fix up their labels.</p> */ static private void AddLabelUpdate() throws NotLoggedInException { boolean success = true; List<DBObject> objects = internalSession.getDBSession().getTransactionalObjects(SchemaConstants.ObjectEventBase); if (objects.size() <= 0) { return; } internalSession.openTransaction("Ganymede objectEvent fixup hook"); try { for (DBObject object: objects) { StringDBField labelField = object.getStringField(SchemaConstants.ObjectEventLabel); if (labelField == null) { objectEventCustom objectEventObj = (objectEventCustom) internalSession.getDBSession().editDBObject(object.getInvid()); if (objectEventObj != null) { ReturnVal retVal = objectEventObj.updateLabel( (String) objectEventObj.getFieldValueLocal(SchemaConstants.ObjectEventObjectName), (String) objectEventObj.getFieldValueLocal(SchemaConstants.ObjectEventToken)); if (!ReturnVal.didSucceed(retVal)) { success = false; } } } } if (success) { internalSession.commitTransaction(); } else { internalSession.abortTransaction(); } } catch (Throwable ex) { internalSession.abortTransaction(); } } /** * <p>At DBStore version 2.7, we changed the package name for our * built-in task classes. This method creates a private HashMap, * {@link arlut.csd.ganymede.server.Ganymede#upgradeClassMap}, which holds * a mapping of old class names to new ones for the built-in classes.</p> */ static synchronized private void prepClassMap() { if (upgradeClassMap == null) { upgradeClassMap = new HashMap<String,String>(); upgradeClassMap.put("arlut.csd.ganymede.dumpAndArchiveTask", "arlut.csd.ganymede.server.dumpAndArchiveTask"); upgradeClassMap.put("arlut.csd.ganymede.dumpTask", "arlut.csd.ganymede.server.dumpTask"); upgradeClassMap.put("arlut.csd.ganymede.GanymedeExpirationTask", "arlut.csd.ganymede.server.GanymedeExpirationTask"); upgradeClassMap.put("arlut.csd.ganymede.GanymedeValidationTask", "arlut.csd.ganymede.server.GanymedeValidationTask"); upgradeClassMap.put("arlut.csd.ganymede.GanymedeWarningTask", "arlut.csd.ganymede.server.GanymedeWarningTask"); } } /** * This method scans the database for valid task entries and adds * them to the scheduler. */ static private void registerTasks() throws NotLoggedInException { List<DBObject> objects = internalSession.getDBSession().getTransactionalObjects(SchemaConstants.TaskBase); /* -- */ if (objects != null) { if (objects.size() == 0) { System.err.println(ts.l("registerTasks.empty_builders")); } for (DBObject object: objects) { // At DBStore version 2.7, we changed the package name for // our built-in task classes. If loaded an older file and // we recognize one of the built-in task class names, go // ahead and rewrite it, hacking down into the // StringDBField in a most naughty way. if (db.isLessThan(2,7)) { prepClassMap(); StringDBField taskClassStrF = object.getStringField(SchemaConstants.TaskClass); if (taskClassStrF != null) { String taskClassStr = (String) taskClassStrF.value; if (upgradeClassMap.containsKey(taskClassStr)) { String newClassName = upgradeClassMap.get(taskClassStr); // "Rewriting old system task class {0} as {1}" System.err.println(ts.l("registerTasks.rewritingClass", taskClassStr, newClassName)); // and here's where we force the value into place taskClassStrF.value = newClassName.intern(); } } } if (debug) { System.err.println(ts.l("registerTasks.processing_task", object.toString())); } scheduler.registerTaskObject(object); } } else { System.err.println(ts.l("registerTasks.empty_tasks")); } // register background time-out task scheduler.addPeriodicAction(new Date(System.currentTimeMillis() + 60000), 1, new timeOutTask(), ts.l("registerTasks.idle_task")); // register background memory status updater task scheduler.addPeriodicAction(new Date(System.currentTimeMillis() + 60000), 1, new memoryStatusTask(), ts.l("registerTasks.memory_status_task")); // register garbage collection task without any schedule for // execution.. this is so that the admin can launch it from the // admin console manually if she is feeling silly scheduler.addActionOnDemand(new gcTask(), ts.l("registerTasks.gc_task")); // likewise the GanymedeValidationTask scheduler.addActionOnDemand(new GanymedeValidationTask(), ts.l("registerTasks.validation_task")); } /** * <p>This method schedules all registered builder tasks and Sync * Runners for execution. This method will be called when a user * commits a transaction. If a given task is already running, the * scheduler will make a note that it needs to be rescheduled on * completion.</p> */ static public void runBuilderTasks() { for (scheduleHandle handle: scheduler.getTasksByType(scheduleHandle.TaskType.BUILDER)) { scheduler.demandTask(handle.getName()); } for (scheduleHandle handle: scheduler.getTasksByClass(SyncRunner.class)) { SyncRunner runner = (SyncRunner) handle.task; if (runner.isIncremental() || runner.isFullState()) { scheduler.demandTask(handle.getName()); } } } /** * <p>This method schedules all registered builder tasks for * execution, with an option set that will cause all builder tasks * to consider object bases as changed since the last build, thus * triggering a full external rebuild.</p> */ static public void forceBuilderTasks() { String[] options = {"forcebuild"}; for (scheduleHandle handle: scheduler.getTasksByType(scheduleHandle.TaskType.BUILDER)) { scheduler.demandTask(handle.getName(), options); } // XXX do we want to do something about forcing sync channels // here? } /** * <p>This method scans the database for valid SyncChannel entries and * adds them to the scheduler.</p> */ static private void registerSyncChannels() throws NotLoggedInException { List<DBObject> objects = internalSession.getDBSession().getTransactionalObjects(SchemaConstants.SyncChannelBase); /* -- */ if (objects != null) { if (objects.size() == 0) { System.err.println(ts.l("registerSyncChannels.no_syncs")); } for (DBObject object: objects) { SyncRunner runner = new SyncRunner(object); if (debug) { System.err.println(ts.l("registerSyncChannels.processing_sync", runner.toString())); } registerSyncChannel(runner); } } else { System.err.println(ts.l("registerSyncChannels.no_syncs")); } } /** * <p>This method links the given SyncRunner object into the list of * registered Sync Channels used at transaction commit time, and * makes it available for the scheduler to run.</p> */ static public void registerSyncChannel(SyncRunner channel) { if (debug) { System.err.println(ts.l("registerSyncChannel.debug_register", channel.getName())); } scheduleHandle handle = scheduler.addActionOnDemand(channel, channel.getName()); channel.setScheduleHandle(handle); } /** * <p>This method unlinks the named SyncRunner object from the list * of registered Sync Channels used at transaction commit time, and * removes it from the Ganymede scheduler.</p> */ static public void unregisterSyncChannel(String channelName) { if (debug) { System.err.println(ts.l("unregisterSyncChannel.debug_unregister", channelName)); } scheduler.unregisterTask(channelName); } /** * <p>This method returns a reference to a SyncRunner registered in * the Ganymede scheduler. It does not remove the SyncRunner from * the scheduler, nor do anything other than return a reference to * it.</p> * * <p>In particular, it is not guaranteed that the SyncRunner * returned is not currently running.</p> */ static public SyncRunner getSyncChannel(String channelName) { return (SyncRunner) scheduler.getTask(channelName); } /** * <p>This method is called by the GanymedeBuilderTask base class to * record that the server is processing a build.</p> */ static public void updateBuildStatus() { int p1 = GanymedeBuilderTask.getPhase1Count(); int p2 = GanymedeBuilderTask.getPhase2Count(); // phase 1 can have the database locked, so show that // for preference if (p1 > 0) { GanymedeServer.sendMessageToRemoteSessions(ClientMessage.BUILDSTATUS, "building"); } else if (p2 > 0) { GanymedeServer.sendMessageToRemoteSessions(ClientMessage.BUILDSTATUS, "building2"); } else { GanymedeServer.sendMessageToRemoteSessions(ClientMessage.BUILDSTATUS, "idle"); } } /** * <p>Static accessor to return a GanymedeSession with supergash * authority and both wizards and oversight turned off.</p> */ static public GanymedeSession getInternalSession() { return Ganymede.internalSession; } } /*------------------------------------------------------------------------------ class GanymedeStartupException ------------------------------------------------------------------------------*/ /** * <p>This is a Ganymede-specific Exception that can be thrown during * the server's start up processing.</p> */ class GanymedeStartupException extends Exception { public GanymedeStartupException() { } public GanymedeStartupException(Throwable thr) { super(thr); } public GanymedeStartupException(String mesg) { super(mesg); } public GanymedeStartupException(String mesg, Throwable thr) { super(mesg, thr); } /** * This method is called to handle error processing logic for this * GanymedeStartupException. */ public void process() { this.printStackTrace(); // "Shutting down." Ganymede.debug("\n" + Ganymede.ts.l("main.info_shutting_down") + "\n"); if (Ganymede.server != null) { GanymedeServer.shutdown("Error thrown during startup", null); } System.exit(1); } } /*------------------------------------------------------------------------------ class GanymedeSilentStartupException ------------------------------------------------------------------------------*/ /** * <p>This is a Ganymede-specific Exception that can be thrown during * the server's start up processing, and which will cause the server * to terminate without displaying any message to the console.</p> */ class GanymedeSilentStartupException extends GanymedeStartupException { public GanymedeSilentStartupException() { } public GanymedeSilentStartupException(Throwable thr) { super(thr); } public GanymedeSilentStartupException(String mesg) { super(mesg); } public GanymedeSilentStartupException(String mesg, Throwable thr) { super(mesg, thr); } /** * <p>This method is called to handle error processing logic for * this GanymedeSilentStartupException.</p> * * <p>If we have been given a message, we'll print that to stderr, * otherwise processing will be completely silent.</p> */ public void process() { if (getMessage() != null) { System.err.println(getMessage()); } if (Ganymede.server != null) { GanymedeServer.shutdown("Error thrown during startup", null); } System.exit(1); } }