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; 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); } }