1 package org
.argeo
.app
.servlet
.odk
;
3 import java
.io
.IOException
;
4 import java
.io
.OutputStream
;
6 import java
.nio
.charset
.Charset
;
7 import java
.security
.DigestOutputStream
;
8 import java
.security
.MessageDigest
;
9 import java
.security
.NoSuchAlgorithmException
;
10 import java
.util
.ArrayList
;
11 import java
.util
.List
;
13 import javax
.jcr
.ItemNotFoundException
;
14 import javax
.jcr
.Node
;
15 import javax
.jcr
.NodeIterator
;
16 import javax
.jcr
.Property
;
17 import javax
.jcr
.Repository
;
18 import javax
.jcr
.RepositoryException
;
19 import javax
.jcr
.Session
;
20 import javax
.jcr
.Value
;
21 import javax
.jcr
.nodetype
.NodeType
;
22 import javax
.jcr
.query
.Query
;
23 import javax
.jcr
.query
.QueryResult
;
24 import javax
.jcr
.query
.Row
;
25 import javax
.jcr
.query
.RowIterator
;
26 import javax
.servlet
.ServletException
;
27 import javax
.servlet
.http
.HttpServlet
;
28 import javax
.servlet
.http
.HttpServletRequest
;
29 import javax
.servlet
.http
.HttpServletResponse
;
31 import org
.apache
.commons
.io
.output
.NullOutputStream
;
32 import org
.argeo
.app
.api
.EntityMimeType
;
33 import org
.argeo
.app
.api
.EntityType
;
34 import org
.argeo
.app
.api
.WGS84PosName
;
35 import org
.argeo
.app
.geo
.GeoShapeUtils
;
36 import org
.argeo
.app
.odk
.OrxManifestName
;
37 import org
.argeo
.cms
.auth
.RemoteAuthUtils
;
38 import org
.argeo
.cms
.servlet
.ServletHttpRequest
;
39 import org
.argeo
.cms
.servlet
.ServletUtils
;
40 import org
.argeo
.cms
.util
.CsvWriter
;
41 import org
.argeo
.cms
.util
.DigestUtils
;
42 import org
.argeo
.jcr
.Jcr
;
43 import org
.argeo
.jcr
.JcrException
;
45 /** Describe additional files. */
46 public class OdkManifestServlet
extends HttpServlet
{
47 private static final long serialVersionUID
= 138030510865877478L;
49 private Repository repository
;
52 protected void doGet(HttpServletRequest req
, HttpServletResponse resp
) throws ServletException
, IOException
{
53 resp
.setHeader("X-OpenRosa-Version", "1.0");
54 resp
.setDateHeader("Date", System
.currentTimeMillis());
56 String pathInfo
= req
.getPathInfo();
57 if (pathInfo
.startsWith("//"))
58 pathInfo
= pathInfo
.substring(1);
60 // we force HTTPS since ODK Collect will fail anyhow when sending http
61 // cf. https://forum.getodk.org/t/authentication-for-non-https-schems/32967/4
62 StringBuilder baseServer
= ServletUtils
.getRequestUrlBase(req
, true);
64 Session session
= RemoteAuthUtils
.doAs(() -> Jcr
.login(repository
, null), new ServletHttpRequest(req
));
67 Node node
= session
.getNode(pathInfo
);
68 if (node
.isNodeType(OrxManifestName
.manifest
.get())) {
69 resp
.setContentType(EntityMimeType
.XML
.toHttpContentType());
70 Writer writer
= resp
.getWriter();
71 writer
.append("<?xml version='1.0' encoding='UTF-8' ?>");
72 writer
.append("<manifest xmlns=\"http://openrosa.org/xforms/xformsManifest\">");
73 NodeIterator nit
= node
.getNodes();
74 children
: while (nit
.hasNext()) {
75 Node file
= nit
.nextNode();
76 if (file
.isNodeType(OrxManifestName
.mediaFile
.get())) {
77 EntityMimeType mimeType
= EntityMimeType
78 .find(file
.getProperty(Property
.JCR_MIMETYPE
).getString());
79 Charset charset
= Charset
.forName(file
.getProperty(Property
.JCR_ENCODING
).getString());
81 if (file
.isNodeType(NodeType
.NT_ADDRESS
)) {
84 target
= file
.getProperty(Property
.JCR_ID
).getNode();
85 } catch (ItemNotFoundException e
) {
86 // TODO remove old manifests
89 writer
.append("<mediaFile>");
90 writer
.append("<filename>");
91 // Work around bug in ODK Collect not supporting paths
92 // writer.append(target.getPath().substring(1) + ".xml");
93 writer
.append(target
.getIdentifier() + "." + mimeType
.getDefaultExtension());
94 writer
.append("</filename>");
96 MessageDigest messageDigest
= MessageDigest
.getInstance(DigestUtils
.MD5
);
97 // TODO cache a temp file ?
98 try (DigestOutputStream out
= new DigestOutputStream(NullOutputStream
.NULL_OUTPUT_STREAM
,
100 writeMediaFile(out
, target
, mimeType
, charset
);
101 writer
.append("<hash>");
102 writer
.append("md5sum:" + DigestUtils
.toHexString(out
.getMessageDigest().digest()));
103 writer
.append("</hash>");
105 writer
.append("<downloadUrl>" + baseServer
+ "/api/odk/formManifest" + file
.getPath()
108 writer
.append("</mediaFile>");
112 writer
.append("</manifest>");
113 } else if (node
.isNodeType(OrxManifestName
.mediaFile
.get())) {
114 EntityMimeType mimeType
= EntityMimeType
.find(node
.getProperty(Property
.JCR_MIMETYPE
).getString());
115 Charset charset
= Charset
.forName(node
.getProperty(Property
.JCR_ENCODING
).getString());
116 resp
.setContentType(mimeType
.toHttpContentType(charset
));
117 if (node
.isNodeType(NodeType
.NT_ADDRESS
)) {
118 Node target
= node
.getProperty(Property
.JCR_ID
).getNode();
120 writeMediaFile(resp
.getOutputStream(), target
, mimeType
, charset
);
122 throw new IllegalArgumentException("Unsupported node " + node
);
125 throw new IllegalArgumentException("Unsupported node " + node
);
127 } catch (RepositoryException e
) {
128 throw new JcrException(e
);
129 } catch (NoSuchAlgorithmException e
) {
130 throw new ServletException(e
);
137 protected void writeMediaFile(OutputStream out
, Node target
, EntityMimeType mimeType
, Charset charset
)
138 throws RepositoryException
, IOException
{
139 if (target
.isNodeType(NodeType
.NT_QUERY
)) {
140 Query query
= target
.getSession().getWorkspace().getQueryManager().getQuery(target
);
141 QueryResult queryResult
= query
.execute();
142 List
<String
> columnNames
= new ArrayList
<>();
143 for (String c
: queryResult
.getColumnNames()) {
146 // TODO make it more configurable
147 columnNames
.add("display");
148 columnNames
.add("geometry");
150 if (EntityMimeType
.XML
.equals(mimeType
)) {
151 } else if (EntityMimeType
.CSV
.equals(mimeType
)) {
152 CsvWriter csvWriter
= new CsvWriter(out
, charset
);
153 csvWriter
.writeLine(columnNames
);
154 RowIterator rit
= queryResult
.getRows();
156 while (rit
.hasNext()) {
157 Row row
= rit
.nextRow();
158 Value
[] values
= row
.getValues();
159 List
<String
> lst
= new ArrayList
<>();
160 for (Value value
: values
) {
161 lst
.add(value
.getString());
164 lst
.add(row
.getValue("name").getString() + " (" + row
.getValue("label").getString() + ")");
165 Node field
= row
.getNode("geopoint");
166 if (field
!= null && field
.isNodeType(EntityType
.geopoint
.get())) {
167 double lat
= field
.getProperty(WGS84PosName
.lat
.get()).getDouble();
168 double lon
= field
.getProperty(WGS84PosName
.lon
.get()).getDouble();
169 double alt
= field
.hasProperty(WGS84PosName
.alt
.get())
170 ? field
.getProperty(WGS84PosName
.alt
.get()).getDouble()
172 String geoshape
= GeoShapeUtils
.geoPointToGeoShape(lon
, lat
, alt
);
175 csvWriter
.writeLine(lst
);
178 // corner case of an empty initial database
179 List
<String
> lst
= new ArrayList
<>();
180 for (int i
= 0; i
< columnNames
.size(); i
++)
182 csvWriter
.writeLine(lst
);
186 if (EntityMimeType
.XML
.equals(mimeType
)) {
187 target
.getSession().exportDocumentView(target
.getPath(), out
, true, false);
188 } else if (EntityMimeType
.CSV
.equals(mimeType
)) {
189 CsvWriter csvWriter
= new CsvWriter(out
, charset
);
190 csvWriter
.writeLine(new String
[] { "name", "label" });
191 NodeIterator children
= target
.getNodes();
192 while (children
.hasNext()) {
193 Node child
= children
.nextNode();
194 String label
= Jcr
.getTitle(child
);
195 csvWriter
.writeLine(new String
[] { child
.getIdentifier(), label
});
203 public void setRepository(Repository repository
) {
204 this.repository
= repository
;