package org.akaza.openclinica.ws;
import org.akaza.openclinica.bean.login.UserAccountBean;
import org.akaza.openclinica.bean.managestudy.StudyBean;
import org.akaza.openclinica.bean.submit.DisplayItemBeanWrapper;
import org.akaza.openclinica.bean.submit.crfdata.ODMContainer;
import org.akaza.openclinica.bean.submit.crfdata.SubjectDataBean;
import org.akaza.openclinica.dao.core.CoreResources;
import org.akaza.openclinica.dao.login.UserAccountDAO;
import org.akaza.openclinica.i18n.util.ResourceBundleProvider;
import org.akaza.openclinica.logic.rulerunner.ExecutionMode;
import org.akaza.openclinica.logic.rulerunner.ImportDataRuleRunnerContainer;
import org.akaza.openclinica.service.rule.RuleSetServiceInterface;
import org.akaza.openclinica.web.crfdata.DataImportService;
import org.akaza.openclinica.web.crfdata.ImportCRFInfo;
import org.akaza.openclinica.web.crfdata.ImportCRFInfoContainer;
import org.akaza.openclinica.ws.bean.BaseStudyDefinitionBean;
import org.akaza.openclinica.ws.validator.CRFDataImportValidator;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.Unmarshaller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.validation.DataBinder;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
@Endpoint
public class DataEndpoint {
protected static final Logger LOG = LoggerFactory.getLogger(DataEndpoint.class);
private final String NAMESPACE_URI_V1 = "http://openclinica.org/ws/data/v1";
private final String ODM_HEADER_NAMESPACE = "<ODM xmlns=\"http://www.cdisc.org/ns/odm/v1.3\" targetNamespace=\"http://openclinica.org/ws/data/v1\" xmlns:OpenClinica=\"http://www.openclinica.org/ns/openclinica_odm/v1.3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.cdisc.org/ns/odm/v1.3\">";
private final DataSource dataSource;
private final MessageSource messages;
private final CoreResources coreResources;
private final Locale locale;
private final DataImportService dataImportService = new DataImportService();
private RuleSetServiceInterface ruleSetService;
private TransactionTemplate transactionTemplate;
public DataEndpoint(DataSource dataSource, MessageSource messages, CoreResources coreResources) {
this.dataSource = dataSource;
this.messages = messages;
this.coreResources = coreResources;
this.ruleSetService = getRuleSetService();
this.locale = new Locale("en_US");
}
/**
* if NAMESPACE_URI_V1:importDataRequest execute this method
*
* @return
* @throws Exception
*/
@PayloadRoot(localPart = "importRequest", namespace = NAMESPACE_URI_V1)
public Source importData(@XPathParam("//ODM") final Element odmElement) throws Exception {
return getTransactionTemplate().execute(new TransactionCallback<Source>() {
public Source doInTransaction(TransactionStatus status) {
try {
return importDataInTransaction(odmElement);
} catch (Exception e) {
throw new RuntimeException("Error processing data import request", e);
}
}
});
}
protected Source importDataInTransaction(Element odmElement) throws Exception {
ResourceBundleProvider.updateLocale(new Locale("en_US"));
// logger.debug("rootElement=" + odmElement);
LOG.debug("rootElement=" + odmElement);
// String xml = null;
UserAccountBean userBean = null;
try {
if (odmElement == null) {
return new DOMSource(mapFailConfirmation(null, "Your XML is not well-formed."));
}
// xml = node2String(odmElement);
// xml = xml.replaceAll("<ODM>", this.ODM_HEADER_NAMESPACE);
ODMContainer odmContainer = unmarshallToODMContainer(odmElement);
// Element clinicalDataNode = (Element) odmElement.getElementsByTagName("ClinicalData").item(0);
// String studyUniqueID = clinicalDataNode.getAttribute("StudyOID");
String studyUniqueID = odmContainer.getCrfDataPostImportContainer().getStudyOID();
userBean = getUserAccount();
// CRFDataImportBean crfDataImportBean = new CRFDataImportBean(studyUniqueID, userBean);
BaseStudyDefinitionBean crfDataImportBean = new BaseStudyDefinitionBean(studyUniqueID, userBean);
DataBinder dataBinder = new DataBinder(crfDataImportBean);
Errors errors = dataBinder.getBindingResult();
CRFDataImportValidator crfDataImportValidator = new CRFDataImportValidator(dataSource);
crfDataImportValidator.validate(crfDataImportBean, errors);
if (!errors.hasErrors()) {
StudyBean studyBean = crfDataImportBean.getStudy();
List<DisplayItemBeanWrapper> displayItemBeanWrappers = new ArrayList<DisplayItemBeanWrapper>();
HashMap<Integer, String> importedCRFStatuses = new HashMap<Integer, String>();
List<String> errorMessagesFromValidation = dataImportService.validateMetaData(odmContainer, dataSource, coreResources, studyBean, userBean,
displayItemBeanWrappers, importedCRFStatuses);
if (errorMessagesFromValidation.size() > 0) {
String err_msg = convertToErrorString(errorMessagesFromValidation);
return new DOMSource(mapFailConfirmation(null, err_msg));
}
ImportCRFInfoContainer importCrfInfo = new ImportCRFInfoContainer(odmContainer, dataSource);
errorMessagesFromValidation = dataImportService.validateData(odmContainer, dataSource, coreResources, studyBean, userBean,
displayItemBeanWrappers, importedCRFStatuses);
if (errorMessagesFromValidation.size() > 0) {
String err_msg = convertToErrorString(errorMessagesFromValidation);
return new DOMSource(mapFailConfirmation(null, err_msg));
}
// setup ruleSets to run if applicable
ArrayList<SubjectDataBean> subjectDataBeans = odmContainer.getCrfDataPostImportContainer().getSubjectData();
List<ImportDataRuleRunnerContainer> containers = dataImportService.runRulesSetup(dataSource, studyBean, userBean, subjectDataBeans,
ruleSetService);
List<String> auditMsgs = new DataImportService().submitData(odmContainer, dataSource, studyBean, userBean, displayItemBeanWrappers,
importedCRFStatuses);
// run rules if applicable
List<String> ruleActionMsgs = dataImportService.runRules(studyBean, userBean, containers, ruleSetService, ExecutionMode.SAVE);
List<String> skippedCRFMsgs = getSkippedCRFMessages(importCrfInfo);
return new DOMSource(mapConfirmation(auditMsgs, ruleActionMsgs, skippedCRFMsgs, importCrfInfo));
} else {
return new DOMSource(mapFailConfirmation(errors, null));
}
// //
} catch (Exception e) {
// return new DOMSource(mapFailConfirmation(null,"Your XML is not well-formed. "+ npe.getMessage()));
LOG.error("Error processing data import request", e);
throw new Exception(e);
}
// return new DOMSource(mapConfirmation(xml, studyBean, userBean));
}
private List<String> getSkippedCRFMessages(ImportCRFInfoContainer importCrfInfo) {
List<String> msgList = new ArrayList<String>();
ResourceBundle respage = ResourceBundleProvider.getPageMessagesBundle();
ResourceBundle resword = ResourceBundleProvider.getWordsBundle();
MessageFormat mf = new MessageFormat("");
mf.applyPattern(respage.getString("crf_skipped"));
for (ImportCRFInfo importCrf : importCrfInfo.getImportCRFList()) {
if (!importCrf.isProcessImport()) {
String preImportStatus = "";
if (importCrf.getPreImportStage().isInitialDE())
preImportStatus = resword.getString("initial_data_entry");
else if (importCrf.getPreImportStage().isInitialDE_Complete())
preImportStatus = resword.getString("initial_data_entry_complete");
else if (importCrf.getPreImportStage().isDoubleDE())
preImportStatus = resword.getString("double_data_entry");
else if (importCrf.getPreImportStage().isDoubleDE_Complete())
preImportStatus = resword.getString("data_entry_complete");
else if (importCrf.getPreImportStage().isAdmin_Editing())
preImportStatus = resword.getString("administrative_editing");
else if (importCrf.getPreImportStage().isLocked())
preImportStatus = resword.getString("locked");
else
preImportStatus = resword.getString("invalid");
Object[] arguments = { importCrf.getStudyOID(), importCrf.getStudySubjectOID(), importCrf.getStudyEventOID(), importCrf.getFormOID(),
preImportStatus };
msgList.add(mf.format(arguments));
}
}
return msgList;
}
private ODMContainer unmarshallToODMContainer(Element odmElement) throws Exception {
ResourceBundle respage = ResourceBundleProvider.getPageMessagesBundle();
String xml = node2String(odmElement);
xml = xml.replaceAll("<ODM>", this.ODM_HEADER_NAMESPACE);
if (xml == null)
throw new Exception(respage.getString("unreadable_file"));
Mapping myMap = new Mapping();
// InputStream xsdFile = coreResources.getInputStream("ODM1-3-0.xsd");//new File(propertiesPath + File.separator
// + "ODM1-3-0.xsd");
// InputStream xsdFile2 = coreResources.getInputStream("ODM1-2-1.xsd");//new File(propertiesPath +
// File.separator + "ODM1-2-1.xsd");
InputStream mapInputStream = coreResources.getInputStream("cd_odm_mapping.xml");
myMap.loadMapping(new InputSource(mapInputStream));
Unmarshaller um1 = new Unmarshaller(myMap);
ODMContainer odmContainer = new ODMContainer();
try {
LOG.debug(xml);
// File xsdFileFinal = new File(xsdFile);
// schemaValidator.validateAgainstSchema(xml, xsdFile);
// removing schema validation since we are presented with the chicken v egg error problem
odmContainer = (ODMContainer) um1.unmarshal(new StringReader(xml));
LOG.debug("Found crf data container for study oid: " + odmContainer.getCrfDataPostImportContainer().getStudyOID());
LOG.debug("found length of subject list: " + odmContainer.getCrfDataPostImportContainer().getSubjectData().size());
return odmContainer;
} catch (Exception me1) {
// fail against one, try another
me1.printStackTrace();
LOG.debug("failed in unmarshaling, trying another version = " + me1.getMessage());
// htaycher: use only one schema according to Tom
// try {
// // schemaValidator.validateAgainstSchema(xml, xsdFile2);
// // for backwards compatibility, we also try to validate vs
// // 1.2.1 ODM 06/2008
// odmContainer = (ODMContainer) um1.unmarshal(new StringReader(xml));
// } catch (Exception me2) {
// // not sure if we want to report me2
// me2.printStackTrace();
// // break here with an exception
// logger.debug("found an error with XML: " + me2.getMessage());
// throw new Exception();
//
// }
throw new Exception();
}
}
/**
* Helper Method to get the user account
*
* @return UserAccountBean
*/
private UserAccountBean getUserAccount() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = null;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
UserAccountDAO userAccountDao = new UserAccountDAO(dataSource);
return (UserAccountBean) userAccountDao.findByUserName(username);
}
/**
* Create Error Response
*
* @param confirmation
* @return
* @throws Exception
*/
private Element mapFailConfirmation(Errors errors, String message) throws Exception {
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
Document document = docBuilder.newDocument();
Element responseElement = document.createElementNS(NAMESPACE_URI_V1, "importDataResponse");
Element resultElement = document.createElementNS(NAMESPACE_URI_V1, "result");
String confirmation = messages.getMessage("dataEndpoint.fail", null, "Fail", locale);
resultElement.setTextContent(confirmation);
responseElement.appendChild(resultElement);
if (errors != null) {
for (ObjectError error : errors.getAllErrors()) {
Element errorElement = document.createElementNS(NAMESPACE_URI_V1, "error");
String theMessage = messages.getMessage(error.getCode(), error.getArguments(), locale);
errorElement.setTextContent(theMessage);
responseElement.appendChild(errorElement);
}
}
if (message != null) {
Element msgElement = document.createElementNS(NAMESPACE_URI_V1, "error");
msgElement.setTextContent(message);
responseElement.appendChild(msgElement);
LOG.debug("sending fail message " + message);
}
return responseElement;
}
/**
* Create Response
*
* @param confirmation
* @return
* @throws Exception
*/
private Element mapConfirmation(List<String> auditMsgs, List<String> ruleActionMsgs, List<String> skippedCRFMsgs, ImportCRFInfoContainer importCRFs)
throws Exception {
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
Document document = docBuilder.newDocument();
Element responseElement = document.createElementNS(NAMESPACE_URI_V1, "importDataResponse");
Element resultElement = document.createElementNS(NAMESPACE_URI_V1, "result");
String totalCRFs = String.valueOf(importCRFs.getImportCRFList().size());
String importedCRFs = String.valueOf(importCRFs.getImportCRFList().size() - importCRFs.getCountSkippedEventCrfs());
if (auditMsgs != null) {
String status = auditMsgs.get(0);
if ("fail".equals(status)) {
String confirmation = messages.getMessage("dataEndpoint.fail", null, "Fail", locale);
resultElement.setTextContent(confirmation);
responseElement.appendChild(resultElement);
Element msgElement = document.createElementNS(NAMESPACE_URI_V1, "error");
auditMsgs.remove(0);
StringBuffer output_msg = new StringBuffer("");
for (String mes : auditMsgs) {
output_msg.append(mes);
}
msgElement.setTextContent(output_msg.toString());
responseElement.appendChild(msgElement);
} else if ("warn".equals(status)) {
// set a summary here, and set individual warnings for each DN
String confirmation = messages.getMessage("dataEndpoint.success", new Object[] { importedCRFs, totalCRFs }, "Success", locale);
resultElement.setTextContent(confirmation);
responseElement.appendChild(resultElement);
Element msgElement = document.createElementNS(NAMESPACE_URI_V1, "summary");
msgElement.setTextContent(auditMsgs.get(1));
responseElement.appendChild(msgElement);
String listOfDns = auditMsgs.get(2);
String[] splitListOfDns = listOfDns.split("---");
for (String dn : splitListOfDns) {
Element warning = document.createElementNS(NAMESPACE_URI_V1, "warning");
warning.setTextContent(dn);
responseElement.appendChild(warning);
}
for (String s : skippedCRFMsgs) {
Element skipMsg = document.createElementNS(NAMESPACE_URI_V1, "warning");
skipMsg.setTextContent(s);
responseElement.appendChild(skipMsg);
}
} else {
if (ruleActionMsgs != null && !ruleActionMsgs.isEmpty()) {
// if there is message from rule. Import data success with rule message
String confirmation = messages.getMessage("dataEndpoint.success", new Object[] { importedCRFs, totalCRFs }, "Success", locale);
resultElement.setTextContent(confirmation);
responseElement.appendChild(resultElement);
for (String s : ruleActionMsgs) {
Element ruleMsg = document.createElementNS(NAMESPACE_URI_V1, "rule_action_warning");
ruleMsg.setTextContent(s);
responseElement.appendChild(ruleMsg);
}
} else {
// plain success no warnings
String confirmation = messages.getMessage("dataEndpoint.success", new Object[] { importedCRFs, totalCRFs }, "Success", locale);
resultElement.setTextContent(confirmation);
responseElement.appendChild(resultElement);
}
for (String s : skippedCRFMsgs) {
Element skipMsg = document.createElementNS(NAMESPACE_URI_V1, "warning");
skipMsg.setTextContent(s);
responseElement.appendChild(skipMsg);
}
}
}
return responseElement;
}
private static String node2String(Node node) {
try {
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter outStream = new StringWriter();
DOMSource source = new DOMSource(node);
StreamResult result = new StreamResult(outStream);
transformer.transform(source, result);
return outStream.getBuffer().toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String convertToErrorString(List<String> errorMessages) {
StringBuilder result = new StringBuilder();
for (String str : errorMessages) {
result.append(str + " \n");
}
return result.toString();
}
public RuleSetServiceInterface getRuleSetService() {
return ruleSetService;
}
public void setRuleSetService(RuleSetServiceInterface ruleSetService) {
this.ruleSetService = ruleSetService;
}
public TransactionTemplate getTransactionTemplate() {
return transactionTemplate;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}