Java tutorial
/** * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This software 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this software; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF * site: http://www.fsf.org. */ package org.ut.biolab.medsavant.server; import org.ut.biolab.medsavant.server.serverapi.SessionManager; import org.ut.biolab.medsavant.server.serverapi.ReferenceManager; import org.ut.biolab.medsavant.server.serverapi.LogManager; import org.ut.biolab.medsavant.server.serverapi.NotificationManager; import org.ut.biolab.medsavant.server.serverapi.GeneSetManager; import org.ut.biolab.medsavant.server.serverapi.PatientManager; import org.ut.biolab.medsavant.server.serverapi.UserManager; import org.ut.biolab.medsavant.server.serverapi.CohortManager; import org.ut.biolab.medsavant.server.serverapi.ProjectManager; import org.ut.biolab.medsavant.server.serverapi.AnnotationManager; import org.ut.biolab.medsavant.server.serverapi.NetworkManager; import org.ut.biolab.medsavant.server.serverapi.RegionSetManager; import java.net.InetAddress; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.sql.SQLException; import gnu.getopt.Getopt; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.RMISocketFactory; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ut.biolab.medsavant.server.db.ConnectionController; import org.ut.biolab.medsavant.server.db.admin.SetupMedSavantDatabase; import org.ut.biolab.medsavant.server.db.util.CustomTables; import org.ut.biolab.medsavant.server.db.util.DBUtils; import org.ut.biolab.medsavant.server.serverapi.VariantManager; import org.ut.biolab.medsavant.server.log.EmailLogger; import org.ut.biolab.medsavant.server.mail.Mail; import org.ut.biolab.medsavant.server.ontology.OntologyManager; import org.ut.biolab.medsavant.server.phasing.BEAGLEWrapper; import org.ut.biolab.medsavant.server.serverapi.SettingsManager; import static org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress.ScheduleStatus.SCHEDULED_AS_LONGJOB; import static org.ut.biolab.medsavant.shared.model.MedSavantServerJobProgress.ScheduleStatus.SCHEDULED_AS_SHORTJOB; import org.ut.biolab.medsavant.shared.model.SessionExpiredException; import org.ut.biolab.medsavant.shared.serverapi.MedSavantServerRegistry; import org.ut.biolab.medsavant.shared.util.DirectorySettings; import org.ut.biolab.medsavant.shared.util.VersionSettings; /** * * @author mfiume */ public class MedSavantServerEngine extends MedSavantServerUnicastRemoteObject implements MedSavantServerRegistry { private static final Log LOG = LogFactory.getLog(MedSavantServerEngine.class); //ssl/tls off by default. private static boolean require_ssltls = false; private static boolean require_client_auth = false; //Maximum number of simultaneous 'long' jobs that can execute. If this //amount is exceeded, the method call will block until a thread //becomes available. //(see submitLongJob) private static int maxThreads = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); private static final int MAX_THREAD_KEEPALIVE_TIME = 1440; //in minutes //Maximum number of IO-heavy jobs that can be run simultaneously. //(see MedSavantIOScheduler) Should be <= MAX_THREADS. public static final int MAX_IO_JOBS = maxThreads; public static boolean USE_INFINIDB_ENGINE = false; int listenOnPort; String thisAddress; Registry registry; // rmi registry for lookup the remote objects. private static ExecutorService longThreadPool; private static ExecutorService shortThreadPool; private static void initThreadPools() { longThreadPool = Executors.newFixedThreadPool(maxThreads); ((ThreadPoolExecutor) longThreadPool).setKeepAliveTime(MAX_THREAD_KEEPALIVE_TIME, TimeUnit.MINUTES); shortThreadPool = Executors.newCachedThreadPool(); } public static int getMaxThreads() { return maxThreads; } public static Void runJobInCurrentThread(MedSavantServerJob msj) throws Exception { msj.setScheduleStatus(SCHEDULED_AS_SHORTJOB); return msj.call(); } /** * Submits and runs the current job using the short job executor service, * and immediately returns. An unlimited number of short jobs can be * executing simultaneously. * * NON_BLOCKING. * * @return The pending result of the job. Trying to fetch the result with * the 'get' method of Future will BLOCK. get() will return null upon * successful completion. */ public static Future submitShortJob(Runnable r) { return shortThreadPool.submit(r); } public static Future submitShortJob(MedSavantServerJob msj) { msj.setScheduleStatus(SCHEDULED_AS_SHORTJOB); return shortThreadPool.submit(msj); } /** * Submits and runs the current job using the long job executor service, and * immediately returns. Only MAX_THREADS of long jobs can be executing * simultaneously -- the rest are queued. * * NON_BLOCKING. * * @return The pending result of the job. Trying to fetch the result with * the 'get' method of Future will BLOCK. get() will return null upon * successful completion. */ public static Future submitLongJobOld(Runnable r) { return longThreadPool.submit(r); } public static Future submitLongJob(MedSavantServerJob msj) { msj.setScheduleStatus(SCHEDULED_AS_LONGJOB); return longThreadPool.submit(msj); } public static List<Future<Void>> submitShortJobs(List<MedSavantServerJob> msjs) throws InterruptedException { for (MedSavantServerJob j : msjs) { j.setScheduleStatus(SCHEDULED_AS_SHORTJOB); } return shortThreadPool.invokeAll(msjs); } public static List<Future<Void>> submitLongJobs(List<MedSavantServerJob> msjs) throws InterruptedException { for (MedSavantServerJob j : msjs) { j.setScheduleStatus(SCHEDULED_AS_LONGJOB); } return longThreadPool.invokeAll(msjs); } /** * Submits long jobs and blocks waiting for completion. Make sure to only * call this from another short or long job! This function does not perform * error checking: if you want to know if a job at index i was successful, * invoke returnVal.get(i).get(); and catch the ExecutionException * * @param msjs * @return * @throws InterruptedException * @see ExecutionException */ public static List<Future<Void>> submitLongJobsAndWait(List<MedSavantServerJob> msjs) throws InterruptedException { List<Future<Void>> jobs = submitLongJobs(msjs); for (Future<Void> job : jobs) { try { job.get(); } catch (ExecutionException ex) { } } return jobs; } /** * @return The executor service used for short jobs. An unlimited number of * short jobs can run simultaneously. */ public static ExecutorService getShortExecutorServiceOld() { return shortThreadPool; } /** * @return The executor service used for long jobs. Only MAX_THREADS long * jobs can run simultaneously. */ public static ExecutorService getLongExecutorServiceOld() { return longThreadPool; } public static boolean isClientAuthRequired() { return require_client_auth; } public static boolean isTLSRequired() { return require_ssltls; } public static RMIServerSocketFactory getDefaultServerSocketFactory() { return isTLSRequired() ? new SslRMIServerSocketFactory(null, null, require_client_auth) : RMISocketFactory.getSocketFactory(); } public static RMIClientSocketFactory getDefaultClientSocketFactory() { return isTLSRequired() ? new SslRMIClientSocketFactory() : RMISocketFactory.getSocketFactory(); } public static String getHost() { return host; } public static int getPort() { return port; } public static String getRootName() { return rootName; } public static String getPass() { return pass; } private static String host; private static int port; private static String rootName; private static String pass; public MedSavantServerEngine(String databaseHost, int databasePort, String rootUserName, String password) throws RemoteException, SQLException, SessionExpiredException { host = databaseHost; port = databasePort; rootName = rootUserName; pass = password; try { // get the address of this host. thisAddress = (InetAddress.getLocalHost()).toString(); } catch (Exception e) { throw new RemoteException("Can't get inet address."); } listenOnPort = MedSavantServerUnicastRemoteObject.getListenPort(); if (!performPreemptiveSystemCheck()) { System.out.println("System check FAILED, see errors above"); System.exit(1); } System.out.println("Server Information:"); System.out.println(" SERVER VERSION: " + VersionSettings.getVersionString() + "\n" + " SERVER ADDRESS: " + thisAddress + "\n" + " LISTENING ON PORT: " + listenOnPort + "\n" + " EXPORT PORT: " + MedSavantServerUnicastRemoteObject.getExportPort() + "\n" + " MAX THREADS: " + maxThreads + "\n" + " MAX IO THREADS: " + MAX_IO_JOBS + "\n"); //+ " EXPORTING ON PORT: " + MedSavantServerUnicastRemoteObject.getExportPort()); try { // create the registry and bind the name and object. if (isTLSRequired()) { System.out.println("SSL/TLS Encryption is enabled, Client authentication is " + (isClientAuthRequired() ? "required." : "NOT required.")); } else { System.out.println("SSL/TLS Encryption is NOT enabled"); //registry = LocateRegistry.createRegistry(listenOnPort); } registry = LocateRegistry.createRegistry(listenOnPort, getDefaultClientSocketFactory(), getDefaultServerSocketFactory()); //TODO: get these from the user ConnectionController.setHost(databaseHost); ConnectionController.setPort(databasePort); System.out.println(); System.out.println("Database Information:"); System.out.println(" DATABASE ADDRESS: " + databaseHost + "\n" + " DATABASE PORT: " + databasePort); System.out.println(" DATABASE USER: " + rootUserName); if (password == null) { System.out.print(" PASSWORD FOR " + rootUserName + ": "); System.out.flush(); char[] pass = System.console().readPassword(); password = new String(pass); } System.out.print("Connecting to database ... "); try { ConnectionController.connectOnce(databaseHost, databasePort, "", rootUserName, password); } catch (SQLException ex) { System.out.println("FAILED"); throw ex; } System.out.println("OK"); bindAdapters(registry); System.out.println("\nServer initialized, waiting for incoming connections..."); EmailLogger.logByEmail("Server booted", "The MedSavant Server Engine successfully booted."); } catch (RemoteException e) { throw e; } catch (SessionExpiredException e) { throw e; } } static public void main(String args[]) { System.out.println("== MedSavant Server Engine ==\n"); try { /** * Override with commands from the command line */ Getopt g = new Getopt("MedSavantServerEngine", args, "c:l:h:p:u:e:"); // int c; String user = "root"; String password = null; String host = "localhost"; int port = 5029; // print usage if (args.length > 0 && args[0].equals("--help")) { System.out.println( "java -jar -Djava.rmi.server.hostname=<hostname> MedSavantServerEngine.jar { [-c CONFIG_FILE] } or { [-l RMI_PORT] [-h DATABASE_HOST] [-p DATABASE_PORT] [-u DATABASE_ROOT_USER] [-e ADMIN_EMAIL] }"); System.out.println("\n\tCONFIG_FILE should be a file containing any number of these keys:\n" + "\t\tdb-user - the database user\n" + "\t\tdb-password - the database password\n" + "\t\tdb-host - the database host\n" + "\t\tdb-port - the database port\n" + "\t\tlisten-on-port - the port on which clients will connect\n" + "\t\temail - the email address to send important notifications\n" + "\t\ttmp-dir - the directory to use for temporary files\n" + "\t\tms-dir - the directory to use to store permanent files\n" + "\t\tencryption - indicate whether encryption should be disabled ('disabled'), enabled without requiring a client certificate ('no_client_auth'), or enabled with requirement for a client certificate ('with_client_auth')\n" + "\t\tkeyStore - full path to the key store\n" + "\t\tkeyStorePass - password for the key store\n" + "\t\tmax-threads - maximum number of allowed "); return; } while ((c = g.getopt()) != -1) { switch (c) { case 'c': String configFileName = g.getOptarg(); System.out.println( "Loading configuration from " + (new File(configFileName)).getAbsolutePath() + " ..."); Properties prop = new Properties(); try { prop.load(new FileInputStream(configFileName)); if (prop.containsKey("db-user")) { user = prop.getProperty("db-user"); } if (prop.containsKey("db-password")) { password = prop.getProperty("db-password"); } if (prop.containsKey("max-threads")) { maxThreads = Integer.parseInt(prop.getProperty("max-threads")); } if (prop.containsKey("db-host")) { host = prop.getProperty("db-host"); } if (prop.containsKey("db-port")) { port = Integer.parseInt(prop.getProperty("db-port")); } if (prop.containsKey("listen-on-port")) { int listenOnPort = Integer.parseInt(prop.getProperty("listen-on-port")); MedSavantServerUnicastRemoteObject.setListenPort(listenOnPort); //MedSavantServerUnicastRemoteObject.setExportPort(listenOnPort + 1); } if (prop.containsKey("mail-un") && prop.containsKey("mail-pw") && prop.containsKey("smtp-server") && prop.containsKey("mail-port")) { Mail.setMailCredentials(prop.getProperty("mail-un"), prop.getProperty("mail-pw"), prop.getProperty("smtp-server"), Integer.parseInt(prop.getProperty("mail-port"))); } if (prop.containsKey("email")) { EmailLogger.setMailRecipient(prop.getProperty("email")); } if (prop.containsKey("tmp-dir")) { DirectorySettings.setTmpDirectory(prop.getProperty("tmp-dir")); } if (prop.containsKey("ms-dir")) { DirectorySettings.setMedSavantDirectory(prop.getProperty("ms-dir")); } if (prop.containsKey("encryption")) { String p = prop.getProperty("encryption"); if (p.equalsIgnoreCase("disabled")) { require_ssltls = false; require_client_auth = false; } else if (p.equalsIgnoreCase("no_client_auth")) { require_ssltls = true; require_client_auth = false; } else if (p.equalsIgnoreCase("with_client_auth")) { require_ssltls = true; require_client_auth = true; } else { throw new IllegalArgumentException( "Uncrecognized value for property 'encryption': " + p); } if (require_ssltls) { if (prop.containsKey("keyStore")) { System.setProperty("javax.net.ssl.keyStore", prop.getProperty("keyStore")); } else { System.err.println("WARNING: No keyStore specified in configuration"); } if (prop.containsKey("keyStorePass")) { System.setProperty("javax.net.ssl.keyStorePassword", prop.getProperty("keyStorePass")); } else { throw new IllegalArgumentException( "ERROR: No keyStore password specified in configuration"); } } } } catch (Exception e) { System.err.println("ERROR: Could not load properties file " + configFileName + ", " + e); } break; case 'h': System.out.println("Host " + g.getOptarg()); host = g.getOptarg(); break; case 'p': port = Integer.parseInt(g.getOptarg()); break; case 'l': int listenOnPort = Integer.parseInt(g.getOptarg()); MedSavantServerUnicastRemoteObject.setListenPort(listenOnPort); //MedSavantServerUnicastRemoteObject.setExportPort(listenOnPort + 1); break; case 'u': user = g.getOptarg(); break; case 'e': EmailLogger.setMailRecipient(g.getOptarg()); break; case '?': break; // getopt() already printed an error default: System.out.print("getopt() returned " + c + "\n"); } } initThreadPools(); new MedSavantServerEngine(host, port, user, password); } catch (Exception e) { e.printStackTrace(); LOG.error("Exiting with exception", e); System.exit(1); } } private void bindAdapters(Registry registry) throws RemoteException, SessionExpiredException { System.out.print("Initializing server registry ... "); System.out.flush(); registry.rebind(SESSION_MANAGER, SessionManager.getInstance()); registry.rebind(CUSTOM_TABLES_MANAGER, CustomTables.getInstance()); registry.rebind(ANNOTATION_MANAGER, AnnotationManager.getInstance()); registry.rebind(COHORT_MANAGER, CohortManager.getInstance()); registry.rebind(GENE_SET_MANAGER, GeneSetManager.getInstance()); registry.rebind(LOG_MANAGER, LogManager.getInstance()); registry.rebind(NETWORK_MANAGER, NetworkManager.getInstance()); registry.rebind(ONTOLOGY_MANAGER, OntologyManager.getInstance()); registry.rebind(PATIENT_MANAGER, PatientManager.getInstance()); registry.rebind(PROJECT_MANAGER, ProjectManager.getInstance()); registry.rebind(REFERENCE_MANAGER, ReferenceManager.getInstance()); registry.rebind(REGION_SET_MANAGER, RegionSetManager.getInstance()); registry.rebind(SETTINGS_MANAGER, SettingsManager.getInstance()); registry.rebind(USER_MANAGER, UserManager.getInstance()); registry.rebind(VARIANT_MANAGER, VariantManager.getInstance()); registry.rebind(DB_UTIL_MANAGER, DBUtils.getInstance()); registry.rebind(SETUP_MANAGER, SetupMedSavantDatabase.getInstance()); registry.rebind(CUSTOM_TABLES_MANAGER, CustomTables.getInstance()); registry.rebind(NOTIFICATION_MANAGER, NotificationManager.getInstance()); System.out.println("OK"); } private static boolean performPreemptiveSystemCheck() { File tmpDir = DirectorySettings.getTmpDirectory(); File cacheDir = DirectorySettings.getCacheDirectory(); File medsavantDir = DirectorySettings.getMedSavantDirectory(); System.out.println("Directory information:"); System.out.println( " TMP DIRECTORY: " + tmpDir.getAbsolutePath() + " has permissions " + permissions(tmpDir)); System.out.println(" MEDSAVANT DIRECTORY: " + medsavantDir.getAbsolutePath() + " has permissions " + permissions(medsavantDir)); System.out.println( " CACHE DIRECTORY: " + cacheDir.getAbsolutePath() + " has permissions " + permissions(cacheDir)); System.out.println(); boolean passed = true; if (!completelyPermissive(tmpDir)) { System.out.println( "ERROR: " + tmpDir.getAbsolutePath() + " does not have appropriate permissions (require rwx)"); passed = false; } if (!completelyPermissive(medsavantDir)) { System.out.println("ERROR: " + medsavantDir.getAbsolutePath() + " does not have appropriate permissions (require rwx)"); passed = false; } if (!completelyPermissive(cacheDir)) { System.out.println("ERROR: " + cacheDir.getAbsolutePath() + " does not have appropriate permissions (require rwx)"); passed = false; } try { File cacheNow = DirectorySettings.generateDateStampDirectory(cacheDir); if (!completelyPermissive(cacheNow)) { System.out.println("ERROR: Directories created inside " + cacheDir + " do not have appropriate permissions (require rwx)"); passed = false; } cacheNow.delete(); } catch (IOException ex) { System.out.println("ERROR: Couldn't create directory inside " + cacheDir.getAbsolutePath()); passed = false; } try { File tmpNow = DirectorySettings.generateDateStampDirectory(tmpDir); if (!completelyPermissive(tmpNow)) { System.out.println("ERROR: Directories created inside " + tmpDir + " do not have appropriate permissions (require rwx)"); passed = false; } tmpNow.delete(); } catch (IOException ex) { System.out.println("ERROR: Couldn't create directory inside " + tmpDir.getAbsolutePath()); passed = false; } try { BEAGLEWrapper.install(medsavantDir); } catch (IOException iex) { LOG.error(iex); passed = false; } return passed; } private static boolean completelyPermissive(File d) { return d.canRead() && d.canWrite() && d.canExecute(); } private static String permissions(File d) { return (d.canRead() ? "r" : "-") + (d.canWrite() ? "w" : "-") + (d.canExecute() ? "x" : "-"); } }