org.ut.biolab.medsavant.server.MedSavantServerEngine.java Source code

Java tutorial

Introduction

Here is the source code for org.ut.biolab.medsavant.server.MedSavantServerEngine.java

Source

/**
 * 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" : "-");
    }
}