package org.akaza.openclinica.control.admin; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; import javax.xml.parsers.DocumentBuilderFactory; import org.akaza.openclinica.bean.core.Role; import org.akaza.openclinica.bean.core.Utils; import org.akaza.openclinica.bean.rule.FileUploadHelper; import org.akaza.openclinica.bean.submit.CRFVersionBean; import org.akaza.openclinica.control.SpringServletAccess; import org.akaza.openclinica.control.core.SecureController; import org.akaza.openclinica.dao.core.CoreResources; import org.akaza.openclinica.dao.hibernate.CrfDao; import org.akaza.openclinica.dao.hibernate.CrfVersionDao; import org.akaza.openclinica.domain.datamap.CrfBean; import org.akaza.openclinica.domain.datamap.CrfVersion; import org.akaza.openclinica.domain.xform.XformContainer; import org.akaza.openclinica.domain.xform.XformGroup; import org.akaza.openclinica.domain.xform.XformItem; import org.akaza.openclinica.domain.xform.XformParser; import org.akaza.openclinica.domain.xform.dto.Html; import org.akaza.openclinica.exception.OpenClinicaSystemException; import org.akaza.openclinica.i18n.core.LocaleResolver; import org.akaza.openclinica.i18n.util.ResourceBundleProvider; import org.akaza.openclinica.service.crfdata.XformMetaDataService; import org.akaza.openclinica.view.Page; import org.akaza.openclinica.web.InsufficientPermissionException; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.lang.exception.ExceptionUtils; import org.springframework.validation.DataBinder; import org.springframework.validation.Errors; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class CreateXformCRFVersionServlet extends SecureController { Locale locale; FileUploadHelper uploadHelper = new FileUploadHelper(); @Override protected void processRequest() throws Exception { CrfDao crfDao = (CrfDao) SpringServletAccess.getApplicationContext(context).getBean("crfDao"); CrfVersionDao crfVersionDao = (CrfVersionDao) SpringServletAccess.getApplicationContext(context).getBean("crfVersionDao"); Locale locale = LocaleResolver.getLocale(request); ResourceBundleProvider.updateLocale(locale); resword = ResourceBundleProvider.getWordsBundle(locale); // Retrieve submission data from multipart request DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); String submittedCrfName = retrieveFormFieldValue(items, "crfName"); String submittedCrfVersionName = retrieveFormFieldValue(items, "versionName"); String submittedCrfVersionDescription = retrieveFormFieldValue(items, "versionDescription"); String submittedRevisionNotes = retrieveFormFieldValue(items, "revisionNotes"); String submittedXformText = retrieveFormFieldValue(items, "xformText"); CRFVersionBean version = (CRFVersionBean) session.getAttribute("version"); logger.debug("Found original CRF ID for new CRF Version:" + version.getCrfId()); // Create container for holding validation errors DataBinder dataBinder = new DataBinder(new CrfVersion()); Errors errors = dataBinder.getBindingResult(); // Validate all upload form fields were populated validateFormFields(errors, version, submittedCrfName,submittedCrfVersionName,submittedCrfVersionDescription, submittedRevisionNotes,submittedXformText); if (!errors.hasErrors()){ // Parse instance and xform XformParser parser = (XformParser) SpringServletAccess.getApplicationContext(context).getBean("xformParser"); XformContainer container = parseInstance(submittedXformText); Html html = parser.unMarshall(submittedXformText); // Save meta-data in database XformMetaDataService xformService = (XformMetaDataService) SpringServletAccess.getApplicationContext(context).getBean("xformMetaDataService"); try { xformService.createCRFMetaData(version, container, currentStudy, ub, html, submittedCrfName, submittedCrfVersionName, submittedCrfVersionDescription, submittedRevisionNotes, submittedXformText, items, errors); } catch (RuntimeException e) { logger.error("Error encountered while saving CRF: " + e.getMessage()); logger.error(ExceptionUtils.getStackTrace(e)); // If there are no logged validation errors, this was an unanticipated exception // and should be allow to crash the page for now if (!errors.hasErrors()) throw e; } } // Save errors to request so they can be displayed to the user if (errors.hasErrors()) { request.setAttribute("errorList", errors.getAllErrors()); logger.debug("Found at least one error. CRF data not saved."); } else { logger.debug("Didn't find any errors. CRF data saved."); // Save any media files uploaded with xform CrfBean crf = (submittedCrfName == null || submittedCrfName.equals("")) ? crfDao.findByCrfId(version.getCrfId()) : crfDao.findByName(submittedCrfName); CrfVersion newVersion = crfVersionDao.findByNameCrfId(submittedCrfVersionName, crf.getCrfId()); saveAttachedMedia(items, crf, newVersion); } forwardPage(Page.CREATE_XFORM_CRF_VERSION_SERVLET); } private void validateFormFields(Errors errors, CRFVersionBean version, String submittedCrfName, String submittedCrfVersionName, String submittedCrfVersionDescription, String submittedRevisionNotes, String submittedXformText) { // Verify CRF Name is populated if (version.getCrfId() == 0 && (submittedCrfName == null || submittedCrfName.equals(""))) { DataBinder crfDataBinder = new DataBinder(new CrfBean()); Errors crfErrors = crfDataBinder.getBindingResult(); crfErrors.rejectValue("name","crf_val_crf_name_blank",resword.getString("CRF_name")); errors.addAllErrors(crfErrors); } DataBinder crfVersionDataBinder = new DataBinder(new CrfVersion()); Errors crfVersionErrors = crfVersionDataBinder.getBindingResult(); // Verify CRF Version Name is populated if (submittedCrfVersionName == null || submittedCrfVersionName.equals("")) { crfVersionErrors.rejectValue("name","crf_ver_val_name_blank",resword.getString("version_name")); } // Verify CRF Version Description is populated if (submittedCrfVersionDescription == null || submittedCrfVersionDescription.equals("")) { crfVersionErrors.rejectValue("description","crf_ver_val_desc_blank",resword.getString("crf_version_description")); } // Verify CRF Version Revision Notes is populated if (submittedRevisionNotes == null || submittedRevisionNotes.equals("")) { crfVersionErrors.rejectValue("revisionNotes","crf_ver_val_rev_notes_blank",resword.getString("revision_notes")); } // Verify Xform text is populated if (submittedXformText == null || submittedXformText.equals("")) { crfVersionErrors.rejectValue("xform","crf_ver_val_xform_blank",resword.getString("xform")); } errors.addAllErrors(crfVersionErrors); } private XformContainer parseInstance(String xform) throws Exception { // Could use the following xpath to get all leaf nodes in the case // of multiple levels of groups: //*[count(./*) = 0] // For now will assume a structure of /form/item or /form/group/item Document doc = null; try { InputStream stream = new ByteArrayInputStream(xform.getBytes(StandardCharsets.UTF_8)); doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream); NodeList instances = doc.getElementsByTagName("instance"); // All whitespace outside tags gets parsed as Text objects and returned // by the various Node methods. We need to ignore these and // focus on actual Elements Element instance = null; // List<XformItem> items = new ArrayList<XformItem>(); List<XformGroup> groups = new ArrayList<XformGroup>(); // Get the primary instance for (int i = 0; i < instances.getLength(); i++) { Element curInstance = (Element) instances.item(i); if (curInstance instanceof Element) { instance = curInstance; break; } } // Get the form element Element form = null; for (int i = 0; i < instance.getChildNodes().getLength(); i++) { Node curNode = instance.getChildNodes().item(i); if (curNode instanceof Element) { form = (Element) curNode; break; } } // Get the groups and grouped items for (int i = 0; i < form.getChildNodes().getLength(); i++) { if (form.getChildNodes().item(i) instanceof Element && ((Element) form.getChildNodes().item(i)).hasChildNodes() && !((Element) form.getChildNodes().item(i)).getTagName().equals("meta")) { Element group = (Element) form.getChildNodes().item(i); XformGroup newGroup = new XformGroup(); newGroup.setGroupName(group.getTagName()); newGroup.setGroupPath("/" + form.getTagName() + "/" + group.getTagName()); groups.add(newGroup); for (int j = 0; j < group.getChildNodes().getLength(); j++) { if (group.getChildNodes().item(j) instanceof Element) { Element item = (Element) group.getChildNodes().item(j); XformItem newItem = new XformItem(); newItem.setItemPath("/" + form.getTagName() + "/" + group.getTagName() + "/" + item.getTagName()); newItem.setItemName(item.getTagName()); // group is null; newGroup.getItems().add(newItem); } } } } XformContainer container = new XformContainer(); container.setGroups(groups); container.setInstanceName(form.getTagName()); return container; } catch (Exception e) { logger.error(e.getMessage()); logger.error(ExceptionUtils.getStackTrace(e)); throw new Exception(e); } } private String retrieveFormFieldValue(List<FileItem> items, String fieldName) throws Exception { for (FileItem item : items) { if (fieldName.equals(item.getFieldName())) return item.getString("UTF-8"); } logger.warn("Form field '" + fieldName + "' missing from xform submission."); return ""; } private void saveAttachedMedia(List<FileItem> items, CrfBean crf, CrfVersion version) { boolean hasFiles = false; for (FileItem item : items) { if (!item.isFormField() && item.getName() != null && !item.getName().isEmpty()) hasFiles = true; } if (hasFiles) { // Create the directory structure for saving the media String dir = Utils.getCrfMediaFilePath(crf, version); if (!new File(dir).exists()) { new File(dir).mkdirs(); logger.debug("Made the directory " + dir); } // Save any media files for (FileItem item : items) { if (!item.isFormField()) { String fileName = item.getName(); // Some browsers IE 6,7 getName returns the whole path int startIndex = fileName.lastIndexOf('\\'); if (startIndex != -1) { fileName = fileName.substring(startIndex + 1, fileName.length()); } File uploadedFile = new File(dir + File.separator + fileName); try { item.write(uploadedFile); } catch (Exception e) { throw new OpenClinicaSystemException(e.getMessage()); } } } } } @Override protected void mayProceed() throws InsufficientPermissionException { locale = LocaleResolver.getLocale(request); // Make sure xforms are enabled String xformEnabled = CoreResources.getField("xform.enabled"); if (xformEnabled == null || !xformEnabled.equals("true")) { addPageMessage(respage.getString("may_not_create_xforms")); throw new InsufficientPermissionException(Page.MENU_SERVLET, resexception.getString("may_not_create_xforms"), "1"); } if (ub.isSysAdmin()) { return; } Role r = currentRole.getRole(); if (r.equals(Role.STUDYDIRECTOR) || r.equals(Role.COORDINATOR)) { return; } addPageMessage(respage.getString("no_have_correct_privilege_current_study") + respage.getString("change_study_contact_sysadmin")); throw new InsufficientPermissionException(Page.MENU_SERVLET, resexception.getString("may_not_submit_data"), "1"); } }