Deal with incomplete (big) ODK forms
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 2 Nov 2023 08:43:24 +0000 (09:43 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 2 Nov 2023 08:43:24 +0000 (09:43 +0100)
org.argeo.app.core/src/org/argeo/app/xforms/FormSubmissionListener.java
org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java

index 6fee2f96fefd53f7a36ca34264dc25a047de33cb..feea106b01b2bc4e29c1f600489868550df1fbe1 100644 (file)
@@ -4,9 +4,11 @@ import org.argeo.api.acr.Content;
 
 /** Called when a user has received a new form submission. */
 public interface FormSubmissionListener {
+       final static String XML_SUBMISSION_FILE = "xml_submission_file";
+       
        /**
         * Called after a form submission has been stored in the user area. The
         * submission will be deleted if any exception is thrown.
         */
-       void formSubmissionReceived(Content content);
+       void formSubmissionReceived(Content content, boolean isIncomplete);
 }
index 90f339a1e10b43180e58e1e66d6f6257f3b858a2..da79b5ef924f786b52bf866ccb8a453e84d83d75 100644 (file)
@@ -39,13 +39,11 @@ public class OdkSubmissionServlet extends HttpServlet {
        private final static CmsLog log = CmsLog.getLog(OdkSubmissionServlet.class);
 
        private final static String XML_SUBMISSION_FILE = "xml_submission_file";
+       private final static String IS_INCOMPLETE = "*isIncomplete*";
 
        private DateTimeFormatter submissionNameFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmmssSSS")
                        .withZone(ZoneId.from(ZoneOffset.UTC));
 
-//     private Repository repository;
-//     private ContentRepository contentRepository;
-
        private Set<FormSubmissionListener> submissionListeners = new HashSet<>();
 
        private AppUserState appUserState;
@@ -56,30 +54,18 @@ public class OdkSubmissionServlet extends HttpServlet {
                resp.setHeader("X-OpenRosa-Version", "1.0");
                resp.setDateHeader("Date", System.currentTimeMillis());
 
-               // should be set in HEAD? Let's rather use defaults.
-               // resp.setIntHeader("X-OpenRosa-Accept-Content-Length", 1024 * 1024);
-
                RemoteAuthRequest request = new ServletHttpRequest(req);
-//             Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
-//
                CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
 
-//             Session adminSession = null;
-//             try {
-//                     // TODO centralise at a deeper level
-//                     adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-//                     SuiteJcrUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-//             } finally {
-//                     Jcr.logout(adminSession);
-//             }
-
+               boolean isIncomplete = false;
                try {
                        Content sessionDir = appUserState.getOrCreateSessionDir(cmsSession);
                        Node cmsSessionNode = sessionDir.adapt(Node.class);
-                       // Node cmsSessionNode = SuiteJcrUtils.getCmsSessionNode(session, cmsSession);
-                       Node submission = cmsSessionNode.addNode(submissionNameFormatter.format(Instant.now()),
-                                       OrxType.submission.get());
+                       String submissionName = submissionNameFormatter.format(Instant.now());
+                       Node submission = cmsSessionNode.addNode(submissionName, OrxType.submission.get());
+                       String submissionPath = submission.getPath();
                        for (Part part : req.getParts()) {
+                               String partNameSane = JcrUtils.replaceInvalidChars(part.getName());
                                if (log.isTraceEnabled())
                                        log.trace("Part: " + part.getName() + ", " + part.getContentType());
 
@@ -88,6 +74,9 @@ public class OdkSubmissionServlet extends HttpServlet {
                                        cmsSessionNode.getSession().importXML(xml.getPath(), part.getInputStream(),
                                                        ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
 
+                               } else if (part.getName().equals(IS_INCOMPLETE)) {
+                                       isIncomplete = true;
+                                       log.debug("Form submission " + submissionPath + " is incomplete, expecting more to be uploaded...");
                                } else {
                                        Node fileNode;
                                        if (part.getName().endsWith(".jpg")) {
@@ -97,19 +86,17 @@ public class OdkSubmissionServlet extends HttpServlet {
                                                        ImageProcessor imageProcessor = new ImageProcessor(() -> part.getInputStream(),
                                                                        () -> Files.newOutputStream(temp));
                                                        imageProcessor.process();
-                                                       fileNode = JcrUtils.copyStreamAsFile(submission, part.getName(),
-                                                                       Files.newInputStream(temp));
+                                                       fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, Files.newInputStream(temp));
                                                } finally {
                                                        Files.deleteIfExists(temp);
                                                }
                                        } else {
-                                               fileNode = JcrUtils.copyStreamAsFile(submission, part.getName(), part.getInputStream());
+                                               fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, part.getInputStream());
                                        }
                                        String contentType = part.getContentType();
                                        if (contentType != null) {
                                                fileNode.addMixin(NodeType.MIX_MIMETYPE);
                                                fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
-
                                        }
                                        if (part.getName().endsWith(".jpg") || part.getName().endsWith(".png")) {
                                                // TODO meta data and thumbnails
@@ -120,11 +107,14 @@ public class OdkSubmissionServlet extends HttpServlet {
                        cmsSessionNode.getSession().save();
                        try {
                                for (FormSubmissionListener submissionListener : submissionListeners) {
-                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission));
+                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission), isIncomplete);
                                }
                        } catch (Exception e) {
                                log.error("Cannot save submission, cancelling...", e);
-                               submission.remove();
+                               if (cmsSessionNode.getSession().hasPendingChanges())
+                                       cmsSessionNode.getSession().refresh(false);// discard
+                               if (cmsSessionNode.getSession().itemExists(submissionPath))
+                                       submission.remove();
                                cmsSessionNode.getSession().save();
                                resp.setStatus(503);
                                return;
@@ -134,8 +124,6 @@ public class OdkSubmissionServlet extends HttpServlet {
                        log.error("Cannot save submission", e);
                        resp.setStatus(503);
                        return;
-//             } finally {
-//                     Jcr.logout(session);
                }
 
                resp.setStatus(201);
@@ -144,10 +132,6 @@ public class OdkSubmissionServlet extends HttpServlet {
 
        }
 
-//     public void setRepository(Repository repository) {
-//             this.repository = repository;
-//     }
-
        public synchronized void addSubmissionListener(FormSubmissionListener listener) {
                submissionListeners.add(listener);
        }
@@ -156,10 +140,6 @@ public class OdkSubmissionServlet extends HttpServlet {
                submissionListeners.remove(listener);
        }
 
-//     public void setContentRepository(ContentRepository contentRepository) {
-//             this.contentRepository = contentRepository;
-//     }
-
        public void setAppUserState(AppUserState appUserState) {
                this.appUserState = appUserState;
        }