Introduce XML upload.
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 5 Nov 2020 11:05:42 +0000 (12:05 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 5 Nov 2020 11:05:42 +0000 (12:05 +0100)
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OrxType.java [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/odk.cnd
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java
org.argeo.entity.api/src/org/argeo/entity/EntityNames.java
org.argeo.entity.api/src/org/argeo/entity/JcrName.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 (file)
index 0000000..3b5d601
--- /dev/null
@@ -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";
+       }
+
+}
index 0f6fcc8f93cda9313b3b4b9c691d02527342211c..c9fe6bccbd20efb4e63730d25bccb2c89488fcef 100644 (file)
@@ -4,8 +4,6 @@
 <orxManifest = "http://openrosa.org/xforms/xformsManifest">
 <odk = "http://www.opendatakit.org/xforms">
 
-[orxList:xform] > mix:created, mix:lastModified, jcrx:csum, entity:form
-+ h:html (odk:html) = odk:html
 
 [odk:head]
 + h:title (jcrx:xmlvalue) = jcrx:xmlvalue
 [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) *
+
+
index a13f058abb85b62b7b0252ce1a8b97bbae9f91c5..f829c12f92eba407afd9238c6fcceaabb2454a5a 100644 (file)
@@ -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("<xform>");
-                                               sb.append("<formID>" + node.getNode("h:html").getIdentifier() + "</formID>");
+                                               sb.append("<formID>"
+                                                               + node.getNode(OdkNames.H_HTML + "/h:head/xforms:model/xforms:instance/xforms:data")
+                                                                               .getProperty("id").getString()
+                                                               + "</formID>");
                                                sb.append("<name>" + Jcr.getTitle(node) + "</name>");
                                                sb.append("<version>" + versionFormatter.format(JcrUtils.getModified(node)) + "</version>");
+//                                             sb.append("<version>" + versionFormatter.format(JcrUtils.getModified(node)) + "</version>");
                                                sb.append("<hash>md5:" + JcrxApi.getChecksum(node, JcrxApi.MD5) + "</hash>");
                                                if (node.hasProperty(Property.JCR_DESCRIPTION))
                                                        sb.append("<name>" + node.getProperty(Property.JCR_DESCRIPTION).getString() + "</name>");
@@ -125,6 +128,7 @@ public class OdkFormListServlet extends HttpServlet {
                                e.printStackTrace();
                                // TODO error message
                                // resp.sendError(500);
+                               resp.sendError(503);
                        } finally {
                                Jcr.logout(session);
                        }
index 301e1bb433d9bcc52c8768829378677d6416e831..7b727e32591dc4a8fe7c2e22965c09522741316f 100644 (file)
@@ -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 {
index e5f5a3ffa6310cda67599c417f98d6b5f94246fa..862ff3665d9c2341ce2cc8c900a560b9b90db18a 100644 (file)
@@ -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("<OpenRosaResponse xmlns=\"http://openrosa.org/http/response\">"
                                + "<message>Form Received!</message>" + "</OpenRosaResponse>");
 
        }
-       
+
        public void setRepository(Repository repository) {
                this.repository = repository;
        }
index f32c262ab07fc36f1f3bd47f31def6980a8a33d7..a1081cc1edfcb4464ad25eccb53de2b495f60366 100644 (file)
@@ -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";
index 43057aae3284cbee0c267535bbd1f2d74ee3eafe..322c42ebe390171894abd7e70ae2c25ec088b1f3 100644 (file)
@@ -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> {
        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();