From 780fe06055c67f30a1a0b55746f3cd4595532756 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 5 Nov 2020 12:05:42 +0100 Subject: [PATCH] Introduce XML upload. --- .../src/org/argeo/support/odk/OrxType.java | 27 ++++++ .../src/org/argeo/support/odk/odk.cnd | 13 ++- .../odk/servlet/OdkFormListServlet.java | 10 +- .../support/odk/servlet/OdkFormServlet.java | 2 + .../odk/servlet/OdkSubmissionServlet.java | 93 +++++++++++++------ .../src/org/argeo/entity/EntityNames.java | 1 + .../src/org/argeo/entity/JcrName.java | 6 +- 7 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OrxType.java diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OrxType.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OrxType.java new file mode 100644 index 0000000..3b5d601 --- /dev/null +++ b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OrxType.java @@ -0,0 +1,27 @@ +package org.argeo.support.odk; + +import org.argeo.entity.JcrName; + +/** Types related to the http://openrosa.org/xforms/xformsList namespace. */ +public enum OrxType implements JcrName { + submission, xml_submission_file; + + @Override + public String getPrefix() { + return prefix(); + } + + public static String prefix() { + return "orx"; + } + + @Override + public String getNamespace() { + return namespace(); + } + + public static String namespace() { + return "http://openrosa.org/xforms"; + } + +} diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/odk.cnd b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/odk.cnd index 0f6fcc8..c9fe6bc 100644 --- a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/odk.cnd +++ b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/odk.cnd @@ -4,8 +4,6 @@ -[orxList:xform] > mix:created, mix:lastModified, jcrx:csum, entity:form -+ h:html (odk:html) = odk:html [odk:head] + h:title (jcrx:xmlvalue) = jcrx:xmlvalue @@ -37,3 +35,14 @@ [odk:setgeopoint] - event (STRING) - ref (STRING) + +// OpenRosa web API + +[orxList:xform] > mix:created, mix:lastModified, jcrx:csum, entity:form ++ h:html (odk:html) = odk:html + +[orx:submission] > mix:created ++ xml_submission_file (nt:unstructured) = nt:unstructured ++ * (nt:file) * + + diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java index a13f058..f829c12 100644 --- a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java +++ b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java @@ -2,7 +2,6 @@ package org.argeo.support.odk.servlet; import java.io.IOException; import java.io.Writer; -import java.security.AccessControlContext; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -18,7 +17,6 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; -import javax.security.auth.Subject; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -33,6 +31,7 @@ import org.argeo.jcr.Jcr; import org.argeo.jcr.JcrUtils; import org.argeo.jcr.JcrxApi; import org.argeo.support.odk.OdkForm; +import org.argeo.support.odk.OdkNames; import org.argeo.support.odk.OrxListType; /** Lists available forms. */ @@ -95,9 +94,13 @@ public class OdkFormListServlet extends HttpServlet { Node node = nit.nextNode(); if (node.isNodeType(OrxListType.xform.get())) { sb.append(""); - sb.append("" + node.getNode("h:html").getIdentifier() + ""); + sb.append("" + + node.getNode(OdkNames.H_HTML + "/h:head/xforms:model/xforms:instance/xforms:data") + .getProperty("id").getString() + + ""); sb.append("" + Jcr.getTitle(node) + ""); sb.append("" + versionFormatter.format(JcrUtils.getModified(node)) + ""); +// sb.append("" + versionFormatter.format(JcrUtils.getModified(node)) + ""); sb.append("md5:" + JcrxApi.getChecksum(node, JcrxApi.MD5) + ""); if (node.hasProperty(Property.JCR_DESCRIPTION)) sb.append("" + node.getProperty(Property.JCR_DESCRIPTION).getString() + ""); @@ -125,6 +128,7 @@ public class OdkFormListServlet extends HttpServlet { e.printStackTrace(); // TODO error message // resp.sendError(500); + resp.sendError(503); } finally { Jcr.logout(session); } diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java index 301e1bb..7b727e3 100644 --- a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java +++ b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java @@ -35,6 +35,8 @@ public class OdkFormServlet extends HttpServlet { Session session = ServletAuthUtils.doAs(() -> Jcr.login(repository, null), req); String pathInfo = req.getPathInfo(); + if (pathInfo.startsWith("//")) + pathInfo = pathInfo.substring(1); boolean oldApproach = false; try { diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java index e5f5a3f..862ff36 100644 --- a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java +++ b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java @@ -1,24 +1,30 @@ package org.argeo.support.odk.servlet; import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import javax.jcr.ImportUUIDBehavior; +import javax.jcr.Node; import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.cms.servlet.ServletAuthUtils; +import org.argeo.entity.EntityNames; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrUtils; +import org.argeo.support.odk.OrxType; /** Receives a form submission. */ public class OdkSubmissionServlet extends HttpServlet { @@ -27,41 +33,68 @@ public class OdkSubmissionServlet extends HttpServlet { private final static String XML_SUBMISSION_FILE = "xml_submission_file"; + private DateTimeFormatter submissionNameFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmmssSSS") + .withZone(ZoneId.from(ZoneOffset.UTC)); + private Repository repository; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - for (Part part : req.getParts()) { - if (log.isDebugEnabled()) - log.debug("Part: " + part.getName() + ", " + part.getContentType()); - } - Part xmlSubmissionPart = req.getPart(XML_SUBMISSION_FILE); - if (xmlSubmissionPart == null) - throw new ServletException("No " + XML_SUBMISSION_FILE + " part"); - try (InputStream in = xmlSubmissionPart.getInputStream();) { - // pretty print - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - StreamResult result = new StreamResult(new StringWriter()); - StreamSource source = new StreamSource(in); - transformer.transform(source, result); - String xmlString = result.getWriter().toString(); - System.out.println(xmlString); - } catch (TransformerException e) { - e.printStackTrace(); - } - resp.setContentType("text/xml; charset=utf-8"); resp.setHeader("X-OpenRosa-Version", "1.0"); resp.setDateHeader("Date", System.currentTimeMillis()); resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024); + + Session session = ServletAuthUtils.doAs(() -> Jcr.login(repository, null), req); + + try { + Node submissions = JcrUtils.mkdirs(session, + "/" + EntityNames.FORM_BASE + "/" + EntityNames.SUBMISSIONS_BASE); + Node submission = submissions.addNode(submissionNameFormatter.format(Instant.now()), + OrxType.submission.get()); + for (Part part : req.getParts()) { + if (log.isDebugEnabled()) + log.debug("Part: " + part.getName() + ", " + part.getContentType()); + + if (part.getName().equals(XML_SUBMISSION_FILE)) { + Node xml = submission.addNode(XML_SUBMISSION_FILE, NodeType.NT_UNSTRUCTURED); + session.importXML(xml.getPath(), part.getInputStream(), + ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); + +// Part xmlSubmissionPart = req.getPart(XML_SUBMISSION_FILE); +// if (xmlSubmissionPart == null) +// throw new ServletException("No " + XML_SUBMISSION_FILE + " part"); +// try (InputStream in = xmlSubmissionPart.getInputStream();) { +// // pretty print +// Transformer transformer = TransformerFactory.newInstance().newTransformer(); +// transformer.setOutputProperty(OutputKeys.INDENT, "yes"); +// transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); +// StreamResult result = new StreamResult(new StringWriter()); +// StreamSource source = new StreamSource(in); +// transformer.transform(source, result); +// String xmlString = result.getWriter().toString(); +// System.out.println(xmlString); +// } catch (TransformerException e) { +// e.printStackTrace(); +// } + + } else { + JcrUtils.copyStreamAsFile(submission, part.getName(), part.getInputStream()); + } + } + session.save(); + } catch (RepositoryException e) { + e.printStackTrace(); + resp.setStatus(503); + return; + } + resp.setStatus(201); resp.getWriter().write("" + "Form Received!" + ""); } - + public void setRepository(Repository repository) { this.repository = repository; } diff --git a/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java b/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java index f32c262..a1081cc 100644 --- a/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java +++ b/org.argeo.entity.api/src/org/argeo/entity/EntityNames.java @@ -5,6 +5,7 @@ import org.argeo.naming.LdapAttrs; /** Constants used to name entity structures. */ public interface EntityNames { final String FORM_BASE = "form"; + final String SUBMISSIONS_BASE = "submissions"; final String TERM_BASE = "term"; final String ENTITY_DEFINITIONS_PATH = "/entity"; diff --git a/org.argeo.entity.api/src/org/argeo/entity/JcrName.java b/org.argeo.entity.api/src/org/argeo/entity/JcrName.java index 43057aa..322c42e 100644 --- a/org.argeo.entity.api/src/org/argeo/entity/JcrName.java +++ b/org.argeo.entity.api/src/org/argeo/entity/JcrName.java @@ -1,7 +1,10 @@ package org.argeo.entity; +import java.util.function.Supplier; + /** Can be applied to {@link Enum}s in order to generate prefixed names. */ -public interface JcrName { +@FunctionalInterface +public interface JcrName extends Supplier { String name(); default String getPrefix() { @@ -12,6 +15,7 @@ public interface JcrName { return null; } + @Override default String get() { String prefix = getPrefix(); return prefix != null ? prefix + ":" + name() : name(); -- 2.30.2