/* * Created on : 07-11-2013 * Author : Bastian Weinlich */ package de.hpi.i2b2.girix; import java.io.File; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.client.Options; import org.apache.axis2.client.ServiceClient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.SAXException; import de.hpi.i2b2.girix.datavo.i2b2message.BodyType; import de.hpi.i2b2.girix.datavo.i2b2message.MessageHeaderType; import de.hpi.i2b2.girix.datavo.i2b2message.RequestHeaderType; import de.hpi.i2b2.girix.datavo.i2b2message.RequestMessageType; import de.hpi.i2b2.girix.datavo.i2b2message.ResponseHeaderType; import de.hpi.i2b2.girix.datavo.i2b2message.ResponseMessageType; import de.hpi.i2b2.girix.datavo.pdo.ConceptSet; import de.hpi.i2b2.girix.datavo.pdo.EventSet; import de.hpi.i2b2.girix.datavo.pdo.ModifierSet; import de.hpi.i2b2.girix.datavo.pdo.ObservationSet; import de.hpi.i2b2.girix.datavo.pdo.ObserverSet; import de.hpi.i2b2.girix.datavo.pdo.PatientSet; import de.hpi.i2b2.girix.datavo.pdo.query.ItemType; import de.hpi.i2b2.girix.datavo.pdo.query.PatientDataResponseType; import de.hpi.i2b2.girix.datavo.pdo.query.PdoQryHeaderType; import de.hpi.i2b2.girix.datavo.pdo.query.RequestType; import de.hpi.i2b2.girix.datavo.girixconfig.InputType; import de.hpi.i2b2.girix.datavo.girixconfig.OutputType; import de.hpi.i2b2.girix.datavo.girixconfig.RscriptletType; import de.hpi.i2b2.girix.datavo.girixconfig.SettingsType; import de.hpi.i2b2.girix.datavo.girixmessages.AdditionalInputType; import de.hpi.i2b2.girix.datavo.girixmessages.PatientSetsType; import de.hpi.i2b2.girix.datavo.girixmessages.ConceptsType; import de.hpi.i2b2.girix.datavo.girixmessages.RScriptletResultType; import de.hpi.i2b2.girix.datavo.girixmessages.RResultsType; import de.hpi.i2b2.girix.datavo.girixmessages.ResultType; import edu.harvard.i2b2.common.exception.I2B2Exception; import edu.harvard.i2b2.common.util.jaxb.JAXBUnWrapHelper; import edu.harvard.i2b2.common.util.jaxb.JAXBUtilException; //This class coordinates the main work for the GetRResults job public class GetRResultsRequestHandler implements RequestHandler { private static Log log = LogFactory.getLog(GetRResultsRequestHandler.class); private String domain = null; private String username = null; private String password = null; private String project = null; private String sessionKey = null; private String QTSUrl = null; private JRIProcessor jriProcessor; public String handleRequest(RequestMessageType input) throws I2B2Exception { // ============== Process the input ============== // Read out some important informations from message header try { domain = input.getMessageHeader().getSecurity().getDomain(); username = input.getMessageHeader().getSecurity().getUsername(); password = input.getMessageHeader().getSecurity().getPassword().getValue(); project = input.getMessageHeader().getProjectId(); } catch (Exception e) { log.error("Incoming request XML is not valid. Stack trace: " + GIRIXUtil.getStackTraceAsString(e)); throw new I2B2Exception("Error message delivered from server: Incomplete or invalid XML request header"); } // Unwrap request message body and extract information RScriptletResultType girixResType = null; String scriptletDirectoryName = null; PatientSetsType patSetType = null; ConceptsType conceptsType = null; try { JAXBUnWrapHelper unwrapHelper = new JAXBUnWrapHelper(); girixResType = (RScriptletResultType) unwrapHelper.getObjectByClass(input.getMessageBody().getAny(), RScriptletResultType.class); scriptletDirectoryName = girixResType.getRScriptletName(); sessionKey = girixResType.getSessionKey(); QTSUrl = girixResType.getQTSUrl(); patSetType = girixResType.getPatientSets(); conceptsType = girixResType.getConcepts(); if (scriptletDirectoryName == null || QTSUrl == null || patSetType == null || conceptsType == null) { throw new NullPointerException(); } } catch (Exception e) { log.error("Incoming XML request body is not valid or complete. Stack trace: " + GIRIXUtil.getStackTraceAsString(e)); throw new I2B2Exception("Error message delivered from server: Incoming XML request body is not valid or complete."); } AdditionalInputType addInType = girixResType.getAdditionalInput(); String scriptletDirectoryPath = findScriptletDirectory(scriptletDirectoryName); RscriptletType girixType = validateConfig(scriptletDirectoryName, scriptletDirectoryPath); Map<String,String> inputParametersMap = getAdditionalInputParameters(girixType, addInType); List<String[]> outputParametersList = prepareCustomtOutputs(girixType); // Start R jriProcessor = new JRIProcessor(); // Add username suffix to webdir path and create folder String extendedWebdirPath = GIRIXUtil.getWEBDIRPATH() + "/userfiles/" + sessionKey + "/"; setupWebDir(extendedWebdirPath); // Do some preparations in the R environment. Returns File handle to the plot directory (later needed) File plotDir = jriProcessor.prepare(extendedWebdirPath); //List<ItemType> conceptsList = extractConcepts(conceptsType); //String[] conceptNames = getConceptNames(conceptsList); //setupPDO(patSetType, conceptsList); // ============== R processing ============== jriProcessor.assignAdditionalInput(inputParametersMap); // Initialize names of the chosen in R //jriProcessor.assignConceptNames(conceptNames); // Set working directory jriProcessor.setWorkingDirectory(scriptletDirectoryPath); // Run script in R jriProcessor.executeRScript(scriptletDirectoryPath + "/mainscript.r"); // Read out output variables from R List<GIRIXOutputVariable> oV = jriProcessor.getOutputVariables(outputParametersList, extendedWebdirPath); // Flush R workspace jriProcessor.doFinalRTasks(extendedWebdirPath); // Get number of plots short plotNumber = (short) plotDir.listFiles().length; uploadAssets(extendedWebdirPath); // ============== Build and send answer message ============== RResultsType rrt = buildAnswer(girixType.getSettings(), plotNumber, oV); return assembleAnswer(input, rrt); } private String assembleAnswer(RequestMessageType input, RResultsType rrt) throws I2B2Exception { // Assemble Response message and return it ResponseHeaderType respHeaderType = MessageUtil.createResponseHeaderType("DONE", "Processing completed"); de.hpi.i2b2.girix.datavo.i2b2message.ObjectFactory i2b2mesFac = new de.hpi.i2b2.girix.datavo.i2b2message.ObjectFactory(); BodyType bodType = i2b2mesFac.createBodyType(); de.hpi.i2b2.girix.datavo.girixmessages.ObjectFactory girixmesFac = new de.hpi.i2b2.girix.datavo.girixmessages.ObjectFactory(); bodType.getAny().add(girixmesFac.createRResults(rrt)); MessageHeaderType mesHead = MessageUtil.createResponseMessageHeaderType(input.getMessageHeader()); ResponseMessageType respMessageType = MessageUtil.createResponseMessageType(mesHead, respHeaderType, bodType); String response = MessageUtil.convertResponseMessageTypeToXML(respMessageType); return response; } private String findScriptletDirectory(String scriptletDirectoryName) throws I2B2Exception { // Assemble scriptlet path scriptletDirectoryName.replace("/", ""); // for security scriptletDirectoryName.replace("..", ""); // for security scriptletDirectoryName.replace("\\", ""); // for security String scriptletDirectoryPath = GIRIXUtil.getRSCRIPTLETPATH() + "/" + scriptletDirectoryName; File scriptletDirectory = new File(scriptletDirectoryPath); if (! scriptletDirectory.exists() || ! scriptletDirectory.canRead() || ! scriptletDirectory.isDirectory()) { log.error("Scriptlet directory error (Existing? Is a directory? Access rights?) at path: " + scriptletDirectoryPath); throw new I2B2Exception("Error delivered from server: Scriptlet directory not available"); } return scriptletDirectoryPath; } private RscriptletType validateConfig(String scriptletDirectoryName, String scriptletDirectoryPath) throws I2B2Exception { // Validate corresponding config file against XML schema and unmarshall into a JAXB Object RscriptletType girixType = null; try { girixType = GIRIXUtil.validateAndUnmarshallScriptletConfigFile(scriptletDirectoryPath, scriptletDirectoryName); } catch (SAXException e) { log.error("Error (SAXException) while validateAndUnmarshallScriptletConfigFile: " + e.getMessage()); throw new I2B2Exception("Error delivered from server: Error while validating / unmarhsalling config.xml file:\n" + e.getMessage()); } if (girixType == null) { log.error("Error during config file validation (girixType==null)"); throw new I2B2Exception("Error delivered from server: Validation error (girixTaype==null)"); } return girixType; } private String[] getConceptNames(List<ItemType> conceptsList) { // Extract the concept names String[] conceptNames = new String[conceptsList.size()]; for (int i = 0; i < conceptsList.size(); i++) { conceptNames[i] = conceptsList.get(i).getDimDimcode(); } return conceptNames; } private List<ItemType> extractConcepts(ConceptsType conceptsType) throws I2B2Exception { // Extract the concept list List<ItemType> conceptsList = conceptsType.getConcept(); // It's not an error if the concept list is empty but it has to exist! if (conceptsList == null) { log.error("Concepts list missing"); throw new I2B2Exception("Error delivered from server: No concepts list specified"); } return conceptsList; } private void setupPDO(PatientSetsType patSetType, List<ItemType> conceptsList) throws I2B2Exception { JAXBUnWrapHelper unwrapHelper = new JAXBUnWrapHelper(); boolean conceptAvailable = true; if (conceptsList.size() == 0) conceptAvailable = false; // For every specified patient set... int i = 1; for (Integer pst : patSetType.getPatientSetCollId()) { // Extract collection id int collID = pst.intValue(); if (collID == 0) { log.error("Patient set collection ID missing"); throw new I2B2Exception("Error delivered from server: Invalid or missing patient set collection id"); } // ============== Communicate with CRC Cell for patient data ============== // Build message body of CRC request PdoQryHeaderType crcPdoqryheader = MessageUtil.createPDOHeader(); RequestType crcReqType = MessageUtil.createPDORequest(collID, conceptsList); de.hpi.i2b2.girix.datavo.i2b2message.ObjectFactory i2b2mesFac = new de.hpi.i2b2.girix.datavo.i2b2message.ObjectFactory(); BodyType crcBodType = i2b2mesFac.createBodyType(); de.hpi.i2b2.girix.datavo.pdo.query.ObjectFactory pdoQryFac = new de.hpi.i2b2.girix.datavo.pdo.query.ObjectFactory(); crcBodType.getAny().add(pdoQryFac.createPdoheader(crcPdoqryheader)); crcBodType.getAny().add(pdoQryFac.createRequest(crcReqType)); // Assemble request message to CRC cell OMElement crcResult = null; RequestHeaderType crcReqHeaderType = MessageUtil.createRequestHeaderType(); MessageHeaderType crcMesHead = MessageUtil.createRequestMessageHeaderType(domain, username, password, project); RequestMessageType crcReqMessageType = MessageUtil.createRequestMessageType(crcMesHead, crcReqHeaderType, crcBodType); OMElement crcRequest = MessageUtil.convertXMLToOMElement(MessageUtil.convertRequestMessageTypeToXML(crcReqMessageType)); // Uncomment for debugging purposes // log.info("Message to CRC cell:\n\n\n" + MessageUtil.convertRequestMessageTypeToXML(crcReqMessageType) + "\n\n\n"); // Send request message to CRC cell and get answer message Options options = new Options(); options.setTo(new EndpointReference(QTSUrl + "pdorequest")); options.setTransportInProtocol(Constants.TRANSPORT_HTTP); options.setProperty(Constants.Configuration.ENABLE_REST, Constants.VALUE_TRUE); options.setTimeOutInMilliSeconds(25000); ServiceClient sender; try { sender = new ServiceClient(); sender.setOptions(options); crcResult = sender.sendReceive(crcRequest); } catch (AxisFault e) { log.error("Error while sending / receiving message to / from CRC cell"); throw new I2B2Exception("Error delivered from server: Communication with CRC cell failed"); } // Uncomment for debugging purposes // log.info("Answer message from CRC cell: \n\n\n\n" + crcResult + "\n\n\n\n"); // Convert response into JAXB (unmarshall) ResponseMessageType crcRMT = MessageUtil.convertXMLTOResponseMessageType(crcResult.toString()); // Unwrap response message and check for errors ConceptSet crcCS = null; PatientSet crcPS = null; ObservationSet crcOS = null; ModifierSet crcMS = null; EventSet crcES = null; ObserverSet crcObS = null; try { String crcStatusType = crcRMT.getResponseHeader().getResultStatus().getStatus().getType(); if ( ! crcStatusType.equals("DONE")) { log.error("Status type of CRC response is not 'DONE'! Message:\n" + crcResult.toString()); throw new I2B2Exception("Error delivered from server: Status type of CRC response is not 'DONE'. See log files for details."); } // Unwrap message body PatientDataResponseType pdrt = (PatientDataResponseType) unwrapHelper.getObjectByClass(crcRMT.getMessageBody().getAny(), PatientDataResponseType.class); if (pdrt.getPatientData() == null) { throw new NullPointerException(); } crcPS = pdrt.getPatientData().getPatientSet(); if (conceptAvailable) { crcCS = pdrt.getPatientData().getConceptSet(); crcOS = pdrt.getPatientData().getObservationSet().get(0); crcMS = pdrt.getPatientData().getModifierSet(); crcES = pdrt.getPatientData().getEventSet(); crcObS = pdrt.getPatientData().getObserverSet(); if (crcPS == null || crcMS == null || crcES == null || crcObS == null || crcCS == null || crcOS == null) { throw new NullPointerException(); } } } catch (NullPointerException e) { log.error("CRC response invalid or imcomplete"); throw new I2B2Exception("Error delivered from server: CRC response is invalid or incomplete"); } catch (JAXBUtilException e) { log.error("Error while unmarshalling CRC response"); throw new I2B2Exception("Error delivered from server: Unmarshalling CRC response"); } jriProcessor.readDataFrameFromString("girix.patients[[" + i + "]]", CRCResponseParser.parsePatientSet(crcPS), CRCResponseParser.patientsColClasses); if (conceptAvailable) { jriProcessor.readDataFrameFromString("girix.observations[[" + i + "]]", CRCResponseParser.parseObservationSet(crcOS, crcCS), CRCResponseParser.conceptsColClasses); jriProcessor.readDataFrameFromString("girix.events[[" + i + "]]", CRCResponseParser.parseEventSet(crcES), CRCResponseParser.eventsColClasses); jriProcessor.readDataFrameFromString("girix.modifiers[[" + i + "]]", CRCResponseParser.parseModifierSet(crcMS), CRCResponseParser.modifierColClasses); jriProcessor.readDataFrameFromString("girix.observers[[" + i + "]]", CRCResponseParser.parseObserverSet(crcObS), CRCResponseParser.observersColClasses); } i++; } // Error case: No patient data set was specified /*if (i == 1) { log.error("No patient set specified in request message"); throw new I2B2Exception("Error delivered from server: No Patient set specified"); }*/ } private void uploadAssets(String extendedWebdirPath) { String plotDirPath = extendedWebdirPath + "/plots"; String csvDirPath = extendedWebdirPath + "/csv"; String rImageDirPath = extendedWebdirPath + "/RImage"; String uploadURL = GIRIXUtil.getUPLOADURL(); GIRIXFileUploader uploader = new GIRIXFileUploader(uploadURL, sessionKey); File plots = new File(plotDirPath); for (File file : plots.listFiles()) { uploader.uploadFile(file, file.getName(), "plots"); } File csvs = new File(csvDirPath); for (File file : csvs.listFiles()) { uploader.uploadFile(file, file.getName(), "csv"); } File rImages = new File(rImageDirPath); for (File file : rImages.listFiles()) { uploader.uploadFile(file, file.getName(), "RImage"); } } private RResultsType buildAnswer(SettingsType settingsType, short plotNumber, List<GIRIXOutputVariable> oV) { // Assemble body part of response message (=RResultsType) RResultsType rrt = new RResultsType(); List<ResultType> resultList = rrt.getResult(); // Add output variables for (GIRIXOutputVariable outputVar : oV) { ResultType rT = new ResultType(); rT.setName(outputVar.getName()); rT.setDescription(outputVar.getDescription()); rT.setType(outputVar.getType()); rT.setValue(outputVar.getValue()); resultList.add(rT); } rrt.setPlotNumber(plotNumber); rrt.setSessionKey(sessionKey); // Add R output stream text if desired in config file (if not set: passing output is default behaviour) if (settingsType.isPassROutput() == null || settingsType.isPassROutput()) { rrt.setRoutput(JRIProcessor.getROutput()); } // Add R error stream text if desired in config file (if not set: passing errors is default behaviour) if (settingsType.isPassRErrors() == null || settingsType.isPassRErrors()) { rrt.setRerrors(JRIProcessor.getRErrors()); } return rrt; } private void setupWebDir(String extendedWebdirPath) throws I2B2Exception { File extendedWebdirFile = new File(extendedWebdirPath); if (! extendedWebdirFile.exists()) { if (! extendedWebdirFile.mkdirs()) { log.error("Extended webdir subfolder could not be created"); throw new I2B2Exception("Extended webdir subfolder could not be created"); } } } private List<String[]> prepareCustomtOutputs(RscriptletType girixType) { // Create custom output values list if there are any List<String[]> outputParametersList = new LinkedList<String[]>(); if (girixType.getCustomOutputs() != null) { for (OutputType ot : girixType.getCustomOutputs().getOutput()) { String[] sa = new String[2]; if (ot.getName() != null) sa[0] = ot.getName(); else continue; if (ot.getDescription() != null) sa[1] = ot.getDescription(); else sa[1] = ""; outputParametersList.add(sa); } } return outputParametersList; } private Map<String, String> getAdditionalInputParameters(RscriptletType girixType, AdditionalInputType addInType) { // Extract additional input values if there are any Map<String,String> inputParametersMap = new HashMap<String, String>(); if (girixType.getAdditionalInputs() != null) { // For handy access convert the request input parameter list into a map Map<String, String> inputParametersFromRequest = null; if (addInType != null) { inputParametersFromRequest = GIRIXUtil.convertListIntoMapAndDecodeHTML(addInType.getInputParameter()); } // For every specified (in config file!) additional input value... for (InputType ipt : girixType.getAdditionalInputs().getInput()) { String iptNameFromConfig = ipt.getName(); // Check if it is also available in request message if (inputParametersFromRequest != null && inputParametersFromRequest.get(iptNameFromConfig) != null) { // If yes, escape double quotes at first for security reasons... String parValue = inputParametersFromRequest.get(iptNameFromConfig); parValue.replace("\"", "\\\""); // ...and add key/value pair to the later used map inputParametersMap.put(iptNameFromConfig, parValue); } else { // If not, add an empty string as value inputParametersMap.put(iptNameFromConfig, ""); } } } return inputParametersMap; } }