]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.app.servlet.odk/src/org/argeo/app/servlet/odk/OdkSubmissionServlet.java
UI time out working
[gpl/argeo-suite.git] / org.argeo.app.servlet.odk / src / org / argeo / app / servlet / odk / OdkSubmissionServlet.java
1 package org.argeo.app.servlet.odk;
2
3 import java.io.IOException;
4 import java.nio.file.Files;
5 import java.nio.file.Path;
6 import java.time.Instant;
7 import java.time.ZoneId;
8 import java.time.ZoneOffset;
9 import java.time.format.DateTimeFormatter;
10 import java.util.HashSet;
11 import java.util.Set;
12
13 import javax.jcr.ImportUUIDBehavior;
14 import javax.jcr.Node;
15 import javax.jcr.Property;
16 import javax.jcr.nodetype.NodeType;
17 import javax.servlet.ServletException;
18 import javax.servlet.http.HttpServlet;
19 import javax.servlet.http.HttpServletRequest;
20 import javax.servlet.http.HttpServletResponse;
21 import javax.servlet.http.Part;
22
23 import org.argeo.api.acr.Content;
24 import org.argeo.api.cms.CmsLog;
25 import org.argeo.api.cms.CmsSession;
26 import org.argeo.app.api.AppUserState;
27 import org.argeo.app.image.ImageProcessor;
28 import org.argeo.app.odk.OrxType;
29 import org.argeo.app.xforms.FormSubmissionListener;
30 import org.argeo.cms.auth.RemoteAuthRequest;
31 import org.argeo.cms.auth.RemoteAuthUtils;
32 import org.argeo.cms.jcr.acr.JcrContent;
33 import org.argeo.cms.servlet.ServletHttpRequest;
34 import org.argeo.jcr.JcrUtils;
35
36 /** Receives a form submission. */
37 public class OdkSubmissionServlet extends HttpServlet {
38 private static final long serialVersionUID = 7834401404691302385L;
39 private final static CmsLog log = CmsLog.getLog(OdkSubmissionServlet.class);
40
41 private final static String XML_SUBMISSION_FILE = "xml_submission_file";
42 private final static String IS_INCOMPLETE = "*isIncomplete*";
43
44 private DateTimeFormatter submissionNameFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmmssSSS")
45 .withZone(ZoneId.from(ZoneOffset.UTC));
46
47 private Set<FormSubmissionListener> submissionListeners = new HashSet<>();
48
49 private AppUserState appUserState;
50
51 @Override
52 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
53 resp.setContentType("text/xml; charset=utf-8");
54 resp.setHeader("X-OpenRosa-Version", "1.0");
55 resp.setDateHeader("Date", System.currentTimeMillis());
56
57 RemoteAuthRequest request = new ServletHttpRequest(req);
58 CmsSession cmsSession = RemoteAuthUtils.getCmsSession(request);
59
60 boolean isIncomplete = false;
61 try {
62 Content sessionDir = appUserState.getOrCreateSessionDir(cmsSession);
63 Node cmsSessionNode = sessionDir.adapt(Node.class);
64 String submissionName = submissionNameFormatter.format(Instant.now());
65 Node submission = cmsSessionNode.addNode(submissionName, OrxType.submission.get());
66 String submissionPath = submission.getPath();
67 for (Part part : req.getParts()) {
68 String partNameSane = JcrUtils.replaceInvalidChars(part.getName());
69 if (log.isTraceEnabled())
70 log.trace("Part: " + part.getName() + ", " + part.getContentType());
71
72 if (part.getName().equals(XML_SUBMISSION_FILE)) {
73 Node xml = submission.addNode(XML_SUBMISSION_FILE, NodeType.NT_UNSTRUCTURED);
74 cmsSessionNode.getSession().importXML(xml.getPath(), part.getInputStream(),
75 ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
76
77 } else if (part.getName().equals(IS_INCOMPLETE)) {
78 isIncomplete = true;
79 log.debug("Form submission " + submissionPath + " is incomplete, expecting more to be uploaded...");
80 } else {
81 Node fileNode;
82 if (part.getName().endsWith(".jpg")) {
83 // Fix metadata
84 Path temp = Files.createTempFile("image", ".jpg");
85 try {
86 ImageProcessor imageProcessor = new ImageProcessor(() -> part.getInputStream(),
87 () -> Files.newOutputStream(temp));
88 imageProcessor.process();
89 fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, Files.newInputStream(temp));
90 } finally {
91 Files.deleteIfExists(temp);
92 }
93 } else {
94 fileNode = JcrUtils.copyStreamAsFile(submission, partNameSane, part.getInputStream());
95 }
96 String contentType = part.getContentType();
97 if (contentType != null) {
98 fileNode.addMixin(NodeType.MIX_MIMETYPE);
99 fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
100 }
101 if (part.getName().endsWith(".jpg") || part.getName().endsWith(".png")) {
102 // TODO meta data and thumbnails
103 }
104 }
105 }
106
107 cmsSessionNode.getSession().save();
108 try {
109 for (FormSubmissionListener submissionListener : submissionListeners) {
110 submissionListener.formSubmissionReceived(JcrContent.nodeToContent(submission), isIncomplete);
111 }
112 } catch (Exception e) {
113 log.error("Cannot save submission, cancelling...", e);
114 if (cmsSessionNode.getSession().hasPendingChanges())
115 cmsSessionNode.getSession().refresh(false);// discard
116 if (cmsSessionNode.getSession().itemExists(submissionPath))
117 submission.remove();
118 cmsSessionNode.getSession().save();
119 resp.setStatus(503);
120 return;
121 }
122
123 } catch (Exception e) {
124 log.error("Cannot save submission", e);
125 resp.setStatus(503);
126 return;
127 }
128
129 resp.setStatus(201);
130 resp.getWriter().write("<OpenRosaResponse xmlns=\"http://openrosa.org/http/response\">"
131 + "<message>Form Received!</message>" + "</OpenRosaResponse>");
132
133 }
134
135 public synchronized void addSubmissionListener(FormSubmissionListener listener) {
136 submissionListeners.add(listener);
137 }
138
139 public synchronized void removeSubmissionListener(FormSubmissionListener listener) {
140 submissionListeners.remove(listener);
141 }
142
143 public void setAppUserState(AppUserState appUserState) {
144 this.appUserState = appUserState;
145 }
146
147 }