Refactor Argeo APIs
[gpl/argeo-suite.git] / org.argeo.app.servlet.odk / src / org / argeo / app / servlet / odk / OdkSubmissionServlet.java
index 9596ebdba9f0e7b51813f3e55abc97589081455d..68e701d5fc4ed7250692fcced41c35c39d40680e 100644 (file)
@@ -1,6 +1,8 @@
 package org.argeo.app.servlet.odk;
 
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZoneOffset;
@@ -11,9 +13,6 @@ import java.util.Set;
 import javax.jcr.ImportUUIDBehavior;
 import javax.jcr.Node;
 import javax.jcr.Property;
-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;
@@ -21,16 +20,17 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.Part;
 
+import org.argeo.api.acr.Content;
+import org.argeo.api.app.AppUserState;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
-import org.argeo.app.core.SuiteUtils;
+import org.argeo.app.image.ImageProcessor;
 import org.argeo.app.odk.OrxType;
 import org.argeo.app.xforms.FormSubmissionListener;
-import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.auth.RemoteAuthRequest;
 import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.acr.JcrContent;
 import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrUtils;
 
 /** Receives a form submission. */
@@ -39,76 +39,91 @@ 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 Set<FormSubmissionListener> submissionListeners = new HashSet<>();
 
+       private AppUserState appUserState;
+
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                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);
 
                RemoteAuthRequest request = new ServletHttpRequest(req);
-               Session session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), request);
+               CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
 
+               boolean isIncomplete = false;
                try {
-//                     Node submissions = JcrUtils.mkdirs(session,
-//                                     "/" + EntityType.form.get() + "/" + EntityNames.SUBMISSIONS_BASE);
-                       CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
-
-                       ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
-                       Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
-                       Session adminSession = null;
-                       try {
-                               // TODO centralise at a deeper level
-                               adminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-                               SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
-                       } finally {
-                               Jcr.logout(adminSession);
-                               Thread.currentThread().setContextClassLoader(currentContextCl);
-                       }
-
-                       Node cmsSessionNode = SuiteUtils.getCmsSessionNode(session, cmsSession);
-                       Node submission = cmsSessionNode.addNode(submissionNameFormatter.format(Instant.now()),
-                                       OrxType.submission.get());
+                       Content sessionDir = appUserState.getOrCreateSessionDir(cmsSession);
+                       Node cmsSessionNode = sessionDir.adapt(Node.class);
+                       String submissionName = submissionNameFormatter.format(Instant.now());
+                       Node submission = cmsSessionNode.addNode(submissionName, OrxType.submission.get());
+                       String submissionPath = submission.getPath();
                        for (Part part : req.getParts()) {
-                               if (log.isDebugEnabled())
-                                       log.debug("Part: " + part.getName() + ", " + part.getContentType());
+                               String partNameSane = JcrUtils.replaceInvalidChars(part.getName());
+                               if (log.isTraceEnabled())
+                                       log.trace("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(),
+                                       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 = JcrUtils.copyStreamAsFile(submission, part.getName(), part.getInputStream());
+                                       Node fileNode;
+                                       if (part.getName().endsWith(".jpg")) {
+                                               // Fix metadata
+                                               Path temp = Files.createTempFile("image", ".jpg");
+                                               try {
+                                                       ImageProcessor imageProcessor = new ImageProcessor(() -> part.getInputStream(),
+                                                                       () -> Files.newOutputStream(temp));
+                                                       imageProcessor.process();
+                                                       fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, Files.newInputStream(temp));
+                                               } finally {
+                                                       Files.deleteIfExists(temp);
+                                               }
+                                       } else {
+                                               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
                                        }
                                }
                        }
-                       session.save();
-                       for (FormSubmissionListener submissionListener : submissionListeners) {
-                               submissionListener.formSubmissionReceived(submission);
+
+                       cmsSessionNode.getSession().save();
+                       try {
+                               for (FormSubmissionListener submissionListener : submissionListeners) {
+                                       submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission), isIncomplete);
+                               }
+                       } catch (Exception e) {
+                               log.error("Cannot save submission, cancelling...", e);
+                               if (cmsSessionNode.getSession().hasPendingChanges())
+                                       cmsSessionNode.getSession().refresh(false);// discard
+                               if (cmsSessionNode.getSession().itemExists(submissionPath))
+                                       submission.remove();
+                               cmsSessionNode.getSession().save();
+                               resp.setStatus(503);
+                               return;
                        }
-               } catch (RepositoryException e) {
-                       e.printStackTrace();
+
+               } catch (Exception e) {
+                       log.error("Cannot save submission", e);
                        resp.setStatus(503);
                        return;
-               } finally {
-                       Jcr.logout(session);
                }
 
                resp.setStatus(201);
@@ -117,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);
        }
@@ -128,4 +139,9 @@ public class OdkSubmissionServlet extends HttpServlet {
        public synchronized void removeSubmissionListener(FormSubmissionListener listener) {
                submissionListeners.remove(listener);
        }
+
+       public void setAppUserState(AppUserState appUserState) {
+               this.appUserState = appUserState;
+       }
+
 }