1 package org
.argeo
.app
.servlet
.odk
;
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
;
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
;
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
;
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);
41 private final static String XML_SUBMISSION_FILE
= "xml_submission_file";
42 private final static String IS_INCOMPLETE
= "*isIncomplete*";
44 private DateTimeFormatter submissionNameFormatter
= DateTimeFormatter
.ofPattern("YYYY-MM-dd-HHmmssSSS")
45 .withZone(ZoneId
.from(ZoneOffset
.UTC
));
47 private Set
<FormSubmissionListener
> submissionListeners
= new HashSet
<>();
49 private AppUserState appUserState
;
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());
57 RemoteAuthRequest request
= new ServletHttpRequest(req
);
58 CmsSession cmsSession
= RemoteAuthUtils
.getCmsSession(request
);
60 boolean isIncomplete
= false;
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());
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
);
77 } else if (part
.getName().equals(IS_INCOMPLETE
)) {
79 log
.debug("Form submission " + submissionPath
+ " is incomplete, expecting more to be uploaded...");
82 if (part
.getName().endsWith(".jpg")) {
84 Path temp
= Files
.createTempFile("image", ".jpg");
86 ImageProcessor imageProcessor
= new ImageProcessor(() -> part
.getInputStream(),
87 () -> Files
.newOutputStream(temp
));
88 imageProcessor
.process();
89 fileNode
= JcrUtils
.copyStreamAsFile(submission
, partNameSane
, Files
.newInputStream(temp
));
91 Files
.deleteIfExists(temp
);
94 fileNode
= JcrUtils
.copyStreamAsFile(submission
, partNameSane
, part
.getInputStream());
96 String contentType
= part
.getContentType();
97 if (contentType
!= null) {
98 fileNode
.addMixin(NodeType
.MIX_MIMETYPE
);
99 fileNode
.setProperty(Property
.JCR_MIMETYPE
, contentType
);
101 if (part
.getName().endsWith(".jpg") || part
.getName().endsWith(".png")) {
102 // TODO meta data and thumbnails
107 cmsSessionNode
.getSession().save();
109 for (FormSubmissionListener submissionListener
: submissionListeners
) {
110 submissionListener
.formSubmissionReceived(JcrContent
.nodeToContent(submission
), isIncomplete
);
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
))
118 cmsSessionNode
.getSession().save();
123 } catch (Exception e
) {
124 log
.error("Cannot save submission", e
);
130 resp
.getWriter().write("<OpenRosaResponse xmlns=\"http://openrosa.org/http/response\">"
131 + "<message>Form Received!</message>" + "</OpenRosaResponse>");
135 public synchronized void addSubmissionListener(FormSubmissionListener listener
) {
136 submissionListeners
.add(listener
);
139 public synchronized void removeSubmissionListener(FormSubmissionListener listener
) {
140 submissionListeners
.remove(listener
);
143 public void setAppUserState(AppUserState appUserState
) {
144 this.appUserState
= appUserState
;