org.ut.biolab.medsavant.MedSavantServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.ut.biolab.medsavant.MedSavantServlet.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;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.NoRouteToHostException;
import java.rmi.ConnectIOException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Semaphore;

import javax.net.ssl.SSLHandshakeException;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ut.biolab.medsavant.shared.model.SessionExpiredException;
import org.ut.biolab.medsavant.shared.model.exception.LockException;
import org.ut.biolab.medsavant.shared.serverapi.AnnotationManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.CohortManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.CustomTablesAdapter;
import org.ut.biolab.medsavant.shared.serverapi.DBUtilsAdapter;
import org.ut.biolab.medsavant.shared.serverapi.GeneSetManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.LogManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.MedSavantServerRegistry;
import org.ut.biolab.medsavant.shared.serverapi.NetworkManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.NotificationManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.OntologyManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.PatientManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.ProjectManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.ReferenceManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.RegionSetManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.SessionManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.SettingsManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.SetupAdapter;
import org.ut.biolab.medsavant.shared.serverapi.UserManagerAdapter;
import org.ut.biolab.medsavant.shared.serverapi.VariantManagerAdapter;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.healthmarketscience.sqlbuilder.Condition;
import java.io.File;
import java.io.FileInputStream;

public class MedSavantServlet extends HttpServlet implements MedSavantServerRegistry {

    private static final int UPLOAD_BUFFER_SIZE = 4096;

    private static final long serialVersionUID = -77006512859078222L;

    private static final String JSON_PARAM_NAME = "json";

    private static final Log LOG = LogFactory.getLog(MedSavantServlet.class);

    private CohortManagerAdapter cohortManager;

    private PatientManagerAdapter patientManager;

    private CustomTablesAdapter customTablesManager;

    private AnnotationManagerAdapter annotationManager;

    private GeneSetManagerAdapter geneSetManager;

    @SuppressWarnings("unused")
    private LogManagerAdapter logManager;

    private NetworkManagerAdapter networkManager;

    private OntologyManagerAdapter ontologyManager;

    private ProjectManagerAdapter projectManager;

    private UserManagerAdapter userManager;

    private SessionManagerAdapter sessionManager;

    private SettingsManagerAdapter settingsManager;

    private RegionSetManagerAdapter regionSetManager;

    private ReferenceManagerAdapter referenceManager;

    private DBUtilsAdapter dbUtils;

    private SetupAdapter setupManager;

    private VariantManagerAdapter variantManager;

    private NotificationManagerAdapter notificationManager;

    @SuppressWarnings("unused")
    private JSONUtilitiesAdapter jsonUtilities;

    private static boolean initialized = false;

    private Gson gson;

    private String medSavantServerHost;

    private int medSavantServerPort;

    private String username;

    private String password;

    private String db;

    private int maxSimultaneousUploads;

    private Semaphore uploadSem;

    private Session session;

    public CohortManagerAdapter getCohortManager() {
        return cohortManager;
    }

    public PatientManagerAdapter getPatientManager() {
        return patientManager;
    }

    public CustomTablesAdapter getCustomTablesManager() {
        return customTablesManager;
    }

    public AnnotationManagerAdapter getAnnotationManager() {
        return annotationManager;
    }

    public GeneSetManagerAdapter getGeneSetManager() {
        return geneSetManager;
    }

    public NetworkManagerAdapter getNetworkManager() {
        return networkManager;
    }

    public OntologyManagerAdapter getOntologyManager() {
        return ontologyManager;
    }

    public ProjectManagerAdapter getProjectManager() {
        return projectManager;
    }

    public UserManagerAdapter getUserManager() {
        return userManager;
    }

    public SessionManagerAdapter getSessionManager() {
        return sessionManager;
    }

    public SettingsManagerAdapter getSettingsManager() {
        return settingsManager;
    }

    public RegionSetManagerAdapter getRegionSetManager() {
        return regionSetManager;
    }

    public ReferenceManagerAdapter getReferenceManager() {
        return referenceManager;
    }

    public DBUtilsAdapter getDbUtils() {
        return dbUtils;
    }

    public SetupAdapter getSetupManager() {
        return setupManager;
    }

    public VariantManagerAdapter getVariantManager() {
        return variantManager;
    }

    public NotificationManagerAdapter getNotificationManager() {
        return notificationManager;
    }

    public String json_invoke(String adapter, String method, String jsonStr) throws IllegalArgumentException {

        adapter = adapter + "Adapter";

        Field selectedAdapter = null;
        for (Field f : MedSavantServlet.class.getDeclaredFields()) {
            if (f.getType().getSimpleName().equalsIgnoreCase(adapter)) {
                selectedAdapter = f;
            }
        }

        if (selectedAdapter == null) {
            throw new IllegalArgumentException("The adapter " + adapter + " does not exist");
        }

        JsonParser parser = new JsonParser();
        JsonArray gArray = parser.parse(jsonStr).getAsJsonArray();
        JsonElement jse = parser.parse(jsonStr);
        JsonArray jsonArray;

        if (jse.isJsonArray()) {
            jsonArray = jse.getAsJsonArray();
        } else {
            throw new IllegalArgumentException("The json method arguments are not an array");
        }

        Method selectedMethod = null;

        for (Method m : selectedAdapter.getType().getMethods()) {
            if (m.getName().equalsIgnoreCase(method)
                    && m.getGenericParameterTypes().length == (jsonArray.size() + 1)) {
                selectedMethod = m;
            }
        }

        if (selectedMethod == null) {
            throw new IllegalArgumentException("The method " + method + " in adapter " + adapter + " with "
                    + jsonArray.size() + " arguments does not exist");
        }

        int i = 0;
        Object[] methodArgs = new Object[selectedMethod.getParameterTypes().length];

        try {
            for (Type t : selectedMethod.getGenericParameterTypes()) {
                LOG.debug("Field " + i + " is " + t.toString() + " for method " + selectedMethod.toString());
                methodArgs[i] = (i > 0) ? gson.fromJson(gArray.get(i - 1), t) : session.getSessionId();
                ++i;
            }
        } catch (JsonParseException je) {
            LOG.error(je);
        }

        Object selectedAdapterInstance = null;
        try {
            selectedAdapterInstance = selectedAdapter.get(this);
            if (selectedAdapterInstance == null) {
                throw new NullPointerException(
                        "Requested adapter " + selectedAdapter.getName() + " was not initialized.");
            }
            MethodInvocation methodInvocation = new MethodInvocation(session, gson, selectedAdapterInstance,
                    selectedMethod, methodArgs);

            return methodInvocation.invoke();
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException("Couldn't execute method with given arguments: " + iae.getMessage());
        } catch (LockException lex) {
            //this shouldn't happen, as locking exceptions can only be thrown by queued method invocations, which
            //are intercepted in the BlockingQueueManager.
            String msg = "Unexpected locking exception thrown in unqueued method invocation.";
            LOG.error(msg);
            throw new IllegalArgumentException(msg + ": " + lex.getMessage());
        }

    }

    @Override
    public void init() throws ServletException {
        LOG.info("MedSavant JSON Client/Server booted.");
        try {
            loadConfiguration();
            initializeRegistry(this.medSavantServerHost, Integer.toString(this.medSavantServerPort));
            session = new Session(sessionManager, username, password, db);
            GsonBuilder gsonBuilder = new GsonBuilder();

            // Handle the condition type.
            gsonBuilder.registerTypeAdapter(Condition.class, new JsonDeserializer<Condition>() {
                @Override
                public Condition deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                        throws JsonParseException {
                    return gson.fromJson(je, SimplifiedCondition.class).getCondition(session.getSessionId(),
                            variantManager);
                }
            });
            gson = gsonBuilder.create();

        } catch (Exception ex) {
            LOG.error(ex);
            throw new ServletException("Failed to initialize the medsavant JSON client: " + ex.getMessage());
        }
    }

    public void initializeRegistry(String serverAddress, String serverPort)
            throws RemoteException, NotBoundException, NoRouteToHostException, ConnectIOException {

        if (initialized) {
            return;
        }

        int port = (new Integer(serverPort)).intValue();

        Registry registry;

        LOG.info("Connecting to MedSavantServerEngine @ " + serverAddress + ":" + serverPort + "...");

        try {
            registry = LocateRegistry.getRegistry(serverAddress, port, new SslRMIClientSocketFactory());
            LOG.debug("Retrieving adapters...");
            setAdaptersFromRegistry(registry);
            setLocalAdapters();
            LOG.info("Connected with SSL/TLS Encryption");
            initialized = true;
        } catch (ConnectIOException ex) {
            if (ex.getCause() instanceof SSLHandshakeException) {
                registry = LocateRegistry.getRegistry(serverAddress, port);
                LOG.info("Retrieving adapters...");
                setAdaptersFromRegistry(registry);
                LOG.info("Connected without SSL/TLS encryption");
            }
        }
        LOG.info("Done");

    }

    private void setLocalAdapters() {
        this.jsonUtilities = new JSONUtilities(this.variantManager, this.annotationManager);
    }

    private void setAdaptersFromRegistry(Registry registry)
            throws RemoteException, NotBoundException, NoRouteToHostException, ConnectIOException {
        this.annotationManager = (AnnotationManagerAdapter) registry.lookup(ANNOTATION_MANAGER);
        this.cohortManager = (CohortManagerAdapter) (registry.lookup(COHORT_MANAGER));
        this.logManager = (LogManagerAdapter) registry.lookup(LOG_MANAGER);
        this.networkManager = (NetworkManagerAdapter) registry.lookup(NETWORK_MANAGER);
        this.ontologyManager = (OntologyManagerAdapter) registry.lookup(ONTOLOGY_MANAGER);
        this.patientManager = (PatientManagerAdapter) registry.lookup(PATIENT_MANAGER);
        this.projectManager = (ProjectManagerAdapter) registry.lookup(PROJECT_MANAGER);
        this.geneSetManager = (GeneSetManagerAdapter) registry.lookup(GENE_SET_MANAGER);
        this.referenceManager = (ReferenceManagerAdapter) registry.lookup(REFERENCE_MANAGER);
        this.regionSetManager = (RegionSetManagerAdapter) registry.lookup(REGION_SET_MANAGER);
        this.sessionManager = (SessionManagerAdapter) registry.lookup(SESSION_MANAGER);
        this.settingsManager = (SettingsManagerAdapter) registry.lookup(SETTINGS_MANAGER);
        this.userManager = (UserManagerAdapter) registry.lookup(USER_MANAGER);
        this.variantManager = (VariantManagerAdapter) registry.lookup(VARIANT_MANAGER);
        this.dbUtils = (DBUtilsAdapter) registry.lookup(DB_UTIL_MANAGER);
        this.setupManager = (SetupAdapter) registry.lookup(SETUP_MANAGER);
        this.customTablesManager = (CustomTablesAdapter) registry.lookup(CUSTOM_TABLES_MANAGER);
        this.notificationManager = (NotificationManagerAdapter) registry.lookup(NOTIFICATION_MANAGER);
        this.jsonUtilities = new JSONUtilities(variantManager, annotationManager);
    }

    private void setExceptionHandler() {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                LOG.info("Global exception handler caught: " + t.getName() + ": " + e);

                if (e instanceof InvocationTargetException) {
                    e = ((InvocationTargetException) e).getCause();
                }

                if (e instanceof SessionExpiredException) {
                    SessionExpiredException see = (SessionExpiredException) e;
                    LOG.error("Session expired exception: " + see.toString());
                    return;
                }
                e.printStackTrace();
            }
        });
    }

    private int copyStreamToServer(InputStream inputStream, String filename, long filesize)
            throws IOException, InterruptedException {

        int streamID = -1;

        try {
            uploadSem.acquire();
            streamID = this.networkManager.openWriterOnServer(session.getSessionId(), filename, filesize);
            int numBytes;
            byte[] buf = new byte[UPLOAD_BUFFER_SIZE];

            while ((numBytes = inputStream.read(buf)) != -1) {
                // System.out.println("Read " + numBytes +" bytes");
                this.networkManager.writeToServer(session.getSessionId(), streamID,
                        ArrayUtils.subarray(buf, 0, numBytes));
            }
        } finally {
            if (streamID >= 0) {
                this.networkManager.closeWriterOnServer(session.getSessionId(), streamID);
            }
            uploadSem.release();
            if (inputStream != null) {
                inputStream.close();
            }
        }

        return streamID;
    }

    private static class Upload {

        String fieldName;
        int streamId;

        public Upload(String fieldName, int streamId) {
            this.fieldName = fieldName;
            this.streamId = streamId;
        }
    }

    private Upload[] handleUploads(FileItemIterator iter)
            throws FileUploadException, IOException, InterruptedException {
        List<Upload> uploads = new ArrayList<Upload>();

        FileItemStream streamToUpload = null;
        long filesize = -1;

        String sn = null;
        String fn = null;

        while (iter.hasNext()) {

            FileItemStream item = iter.next();
            String name = item.getFieldName();
            InputStream stream = item.openStream();
            // System.out.println("Got file " + name);
            if (item.isFormField()) {
                if (name.startsWith("size_")) {
                    sn = name.split("_")[1];
                    filesize = Long.parseLong(Streams.asString(stream));
                }
            } else if (name.startsWith("file_")) {
                streamToUpload = item;
            } else {
                throw new IllegalArgumentException("Unrecognized file detected with field name " + name);
            }
            if (streamToUpload != null) {
                // Do the upload               
                int streamId = copyStreamToServer(streamToUpload.openStream(), streamToUpload.getName(),
                        (sn != null && fn != null && sn.equals(fn)) ? filesize : -1);
                if (streamId >= 0) {
                    uploads.add(new Upload(name, streamId));
                }
            }

        }

        return uploads.toArray(new Upload[uploads.size()]);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // format: ..../adapter/method

        String uri = req.getRequestURI();
        String[] x = uri.split("/");
        int methodIndex;
        int adapterIndex;

        if (uri.endsWith("/")) {
            methodIndex = x.length - 2;
            adapterIndex = x.length - 3;
        } else {
            methodIndex = x.length - 1;
            adapterIndex = x.length - 2;
        }

        resp.setContentType("text/x-json;charset=UTF-8");
        resp.setHeader("Cache-Control", "no-cache");
        try {
            if (adapterIndex >= 0 && x[adapterIndex].equals("UploadManager")
                    && x[methodIndex].startsWith("upload")) {
                if (!ServletFileUpload.isMultipartContent(req)) {
                    throw new IllegalArgumentException(
                            gson.toJson("File upload failed: content is not multipart", String.class));
                }

                FileItemIterator iter = (new ServletFileUpload()).getItemIterator(req);
                Upload[] uploads = handleUploads(iter); // note this BLOCKS until upload is finished.
                resp.getWriter().print(gson.toJson(uploads, uploads.getClass()));
                resp.getWriter().close();
            } else if (methodIndex < 0 || adapterIndex < 0) {
                throw new IllegalArgumentException(gson.toJson("Malformed URL", String.class));
            } else {

                // Print parameter map to stdout
                /*
                 * for (Object o : req.getParameterMap().entrySet()) { Map.Entry e = (Map.Entry) o;
                 * System.out.println("Key="+e.getKey()); for(String a : (String[])e.getValue()){
                 * System.out.println("\tVal="+a); } }
                 */
                String json_args = req.getParameter(JSON_PARAM_NAME);
                if (json_args == null) {
                    json_args = "[]";
                }

                String ret = json_invoke(x[adapterIndex], x[methodIndex], json_args);

                resp.getWriter().print(ret);
                resp.getWriter().close();
            }
        } catch (FileUploadException fue) {
            LOG.error(fue);
        } catch (IllegalArgumentException iae) {
            LOG.error(iae);
        } catch (InterruptedException iex) { // file upload cancelled.
            LOG.error(iex);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        String[] x = uri.split("/");
        int adapterIndex = x.length - 1;

        resp.setContentType("text/x-json;charset=UTF-8");
        resp.setHeader("Cache-Control", "no-cache");
        if (x[adapterIndex].equalsIgnoreCase("testing")) {
            resp.setContentType("text/html;charset=UTF-8");
            resp.setHeader("Cache-Control", "no-cache");
            InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream("/testing.html");
            IOUtils.copy(input, resp.getOutputStream());
            resp.getOutputStream().close();
        } else {
            resp.getWriter().print("Invalid");
            resp.getWriter().close();
        }
    }

    private InputStream getConfigInputStream() throws ServletException, IOException {
        String[] configFileLocations = getServletContext().getInitParameter("MedSavantConfigFile").split(":");

        File f = null;
        for (String cf : configFileLocations) {

            if (!cf.startsWith("/")) {
                LOG.info("Looking for configuration from path relative to servlet context " + cf);
                InputStream in = getServletContext().getResourceAsStream("/" + cf);
                if (in != null) {
                    LOG.info("Reading configuration from /" + cf);
                    return in;
                }
            } else {
                f = new File(cf);
                LOG.info("Looking for config file at: " + f.getAbsolutePath());
                if (f.exists()) {
                    break;
                } else {
                    f = null;
                }
            }
        }
        if (f == null) {
            throw new ServletException("Can't load configuration - no config file found!");
        }

        LOG.info("Reading configuration from " + f.getAbsolutePath());

        return new FileInputStream(f);
    }

    private void loadConfiguration() throws ServletException {
        String host = null;
        String uname = null;
        String pass = null;
        String dbase = null;
        String maxSimultaneousUploadsStr = null;
        int p = -1;
        try {
            Properties props = new Properties();
            InputStream in = getConfigInputStream();
            props.load(in);
            in.close();

            host = props.getProperty("host", "");
            uname = props.getProperty("username", "");
            pass = props.getProperty("password", "");
            dbase = props.getProperty("db", "");
            maxSimultaneousUploadsStr = props.getProperty("max_simultaneous_uploads", "").trim();
            String portStr = props.getProperty("port", "-1").trim();
            if (StringUtils.isBlank(portStr) || !NumberUtils.isNumber(portStr)) {
                LOG.error("No port specified in configuration, cannot continue");
            }
            if (maxSimultaneousUploadsStr == null) {
                throw new ServletException("No maximum number of simultaneous uploads specified.  Cannot continue");
            }
            p = Integer.parseInt(portStr);
            if (p <= 0) {
                throw new ServletException(
                        "Illegal port specified in configuration: " + portStr + ", cannot continue.");
            }

            maxSimultaneousUploads = Integer.parseInt(maxSimultaneousUploadsStr);
            if (maxSimultaneousUploads <= 0) {
                throw new ServletException("Illegal number of maximum simultaneous uploads in configuration: "
                        + maxSimultaneousUploadsStr + ", cannot continue.");
            }

            uploadSem = new Semaphore(maxSimultaneousUploads, true);
            if (StringUtils.isBlank(uname)) {
                throw new ServletException("No username specified in configuration file, cannot continue.");
            }
            if (StringUtils.isBlank(pass)) {
                throw new ServletException("No password specified in configuration file, cannot continue.");
            }
            if (StringUtils.isBlank(dbase)) {
                throw new ServletException("No database specified in configuration file, cannot continue.");
            }
            if (StringUtils.isBlank(host)) {
                throw new ServletException("No host specified in configuration file, cannot continue.");
            }
        } catch (IOException iex) {
            throw new ServletException("IO Exception reading config file, cannot continue: " + iex.getMessage());
        }
        this.medSavantServerHost = host;
        this.medSavantServerPort = p;
        this.username = uname;
        this.password = pass;
        this.db = dbase;

        LOG.info("Configured with:");
        LOG.info("Host = " + host);
        LOG.info("Port = " + p);
        LOG.info("Username = " + uname);
        LOG.info("Database = " + this.db);
    }
}