Introduce ODK forms support.
authorMathieu Baudier <mbaudier@argeo.org>
Thu, 15 Oct 2020 09:25:33 +0000 (11:25 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Thu, 15 Oct 2020 09:25:33 +0000 (11:25 +0200)
18 files changed:
dep/org.argeo.suite.dep.ui.rap/pom.xml
knowledge/org.argeo.support.odk/.classpath [new file with mode: 0644]
knowledge/org.argeo.support.odk/.gitignore [new file with mode: 0644]
knowledge/org.argeo.support.odk/.project [new file with mode: 0644]
knowledge/org.argeo.support.odk/META-INF/.gitignore [new file with mode: 0644]
knowledge/org.argeo.support.odk/OSGI-INF/odkFormListServlet.xml [new file with mode: 0644]
knowledge/org.argeo.support.odk/OSGI-INF/odkFormServlet.xml [new file with mode: 0644]
knowledge/org.argeo.support.odk/OSGI-INF/odkSubmissionServlet.xml [new file with mode: 0644]
knowledge/org.argeo.support.odk/bnd.bnd [new file with mode: 0644]
knowledge/org.argeo.support.odk/build.properties [new file with mode: 0644]
knowledge/org.argeo.support.odk/pom.xml [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/BundleResourceOdkForm.java [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OdkForm.java [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java [new file with mode: 0644]
knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java [new file with mode: 0644]
knowledge/pom.xml [new file with mode: 0644]
pom.xml

index bac0d06ec0c7c8315b6150d08013e97a2d3da909..66858c292421539332b5cbb64dc63e7c1a585985 100644 (file)
                        <artifactId>org.argeo.suite.ui.rap</artifactId>
                        <version>2.1.16-SNAPSHOT</version>
                </dependency>
+               
+               <!-- Argeo Knowledge -->
+               <dependency>
+                       <groupId>org.argeo.suite</groupId>
+                       <artifactId>org.argeo.support.odk</artifactId>
+                       <version>2.1.16-SNAPSHOT</version>
+               </dependency>
 
                <!-- Base CMS distribution -->
                <dependency>
diff --git a/knowledge/org.argeo.support.odk/.classpath b/knowledge/org.argeo.support.odk/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/knowledge/org.argeo.support.odk/.gitignore b/knowledge/org.argeo.support.odk/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/knowledge/org.argeo.support.odk/.project b/knowledge/org.argeo.support.odk/.project
new file mode 100644 (file)
index 0000000..ac46107
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.support.odk</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/knowledge/org.argeo.support.odk/META-INF/.gitignore b/knowledge/org.argeo.support.odk/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/knowledge/org.argeo.support.odk/OSGI-INF/odkFormListServlet.xml b/knowledge/org.argeo.support.odk/OSGI-INF/odkFormListServlet.xml
new file mode 100644 (file)
index 0000000..eebd14e
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ODK Form List Servlet">
+   <implementation class="org.argeo.support.odk.servlet.OdkFormListServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/api/odk/formList"/>
+   <reference bind="addForm" cardinality="0..n" interface="org.argeo.support.odk.OdkForm" name="OdkForm" policy="dynamic" unbind="removeForm"/>
+</scr:component>
diff --git a/knowledge/org.argeo.support.odk/OSGI-INF/odkFormServlet.xml b/knowledge/org.argeo.support.odk/OSGI-INF/odkFormServlet.xml
new file mode 100644 (file)
index 0000000..c2cd89b
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ODK Form Servlet">
+   <implementation class="org.argeo.support.odk.servlet.OdkFormServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/api/odk/*.xml"/>
+   <reference bind="addForm" cardinality="0..n" interface="org.argeo.support.odk.OdkForm" name="OdkForm" policy="dynamic" unbind="removeForm"/>
+</scr:component>
diff --git a/knowledge/org.argeo.support.odk/OSGI-INF/odkSubmissionServlet.xml b/knowledge/org.argeo.support.odk/OSGI-INF/odkSubmissionServlet.xml
new file mode 100644 (file)
index 0000000..77fba54
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ODK Submission Servlet">
+   <implementation class="org.argeo.support.odk.servlet.OdkSubmissionServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/api/odk/submission"/>
+   <property name="osgi.http.whiteboard.servlet.multipart.enabled" type="String" value="true"/>
+</scr:component>
diff --git a/knowledge/org.argeo.support.odk/bnd.bnd b/knowledge/org.argeo.support.odk/bnd.bnd
new file mode 100644 (file)
index 0000000..9291447
--- /dev/null
@@ -0,0 +1,5 @@
+
+Service-Component:\
+OSGI-INF/odkFormListServlet.xml,\
+OSGI-INF/odkFormServlet.xml,\
+OSGI-INF/odkSubmissionServlet.xml
diff --git a/knowledge/org.argeo.support.odk/build.properties b/knowledge/org.argeo.support.odk/build.properties
new file mode 100644 (file)
index 0000000..6210e84
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
diff --git a/knowledge/org.argeo.support.odk/pom.xml b/knowledge/org.argeo.support.odk/pom.xml
new file mode 100644 (file)
index 0000000..ec62f62
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>knowledge</artifactId>
+               <version>2.1.16-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.support.odk</artifactId>
+       <name>ODK support</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.util</artifactId>
+                       <version>${version.argeo-commons}</version>
+               </dependency>
+       </dependencies>
+</project>
diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/BundleResourceOdkForm.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/BundleResourceOdkForm.java
new file mode 100644 (file)
index 0000000..fc55f35
--- /dev/null
@@ -0,0 +1,101 @@
+package org.argeo.support.odk;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.argeo.util.DigestUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/** {@link OdkForm} implementation based on an OSGi {@link Bundle} resource. */
+public class BundleResourceOdkForm implements OdkForm {
+       private String formId;
+       private String name;
+       private String version;
+       private String description;
+       private String hash;
+       private String fileName;
+
+       private byte[] data;
+
+       public void init(Map<String, String> properties, BundleContext bundleContext) throws IOException {
+               String location = properties.get("location");
+               fileName = FilenameUtils.getName(location);
+               URL url = bundleContext.getBundle().getResource(location);
+               data = IOUtils.toByteArray(url.openStream());
+               hash = "md5:" + DigestUtils.digest(DigestUtils.MD5, data);
+
+               // TODO get it from the XML
+               formId = properties.get("formId");
+               version = properties.get("version");
+
+               name = properties.get("name");
+               description = properties.get("description");
+       }
+
+       @Override
+       public String getFormId() {
+               return formId;
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public String getVersion() {
+               return version;
+       }
+
+       @Override
+       public String getDescription() {
+               return description;
+       }
+
+       @Override
+       public String getHash(String hashType) {
+               return hash;
+       }
+
+       @Override
+       public String getFileName() {
+               return fileName;
+       }
+
+       @Override
+       public InputStream openStream() {
+               return new ByteArrayInputStream(data);
+       }
+
+       @Override
+       public int hashCode() {
+               assert formId != null;
+               assert version != null;
+               return formId.hashCode() + version.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               assert formId != null;
+               assert version != null;
+               if (!(obj instanceof OdkForm))
+                       return false;
+               OdkForm other = (OdkForm) obj;
+               assert other.getFormId() != null;
+               assert other.getVersion() != null;
+
+               return other.getFormId().equals(formId) && other.getVersion().equals(version);
+       }
+
+       @Override
+       public String toString() {
+               return "ODK Form " + formId + ", v" + version;
+       }
+
+}
diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OdkForm.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/OdkForm.java
new file mode 100644 (file)
index 0000000..7b79a13
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.support.odk;
+
+import java.io.InputStream;
+
+/** Abstraction of a single ODK form. */
+public interface OdkForm {
+       String getFormId();
+
+       String getName();
+
+       String getVersion();
+
+       String getDescription();
+
+       String getHash(String hashType);
+
+       String getFileName();
+
+       InputStream openStream();
+}
diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormListServlet.java
new file mode 100644 (file)
index 0000000..8398c7f
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.support.odk.servlet;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.support.odk.OdkForm;
+
+/** Lists available forms. */
+public class OdkFormListServlet extends HttpServlet {
+       private static final long serialVersionUID = 2706191315048423321L;
+       private final static Log log = LogFactory.getLog(OdkFormListServlet.class);
+
+       private Set<OdkForm> odkForms = Collections.synchronizedSet(new HashSet<>());
+
+       @Override
+       protected void doGet(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());
+
+               String serverName = req.getServerName();
+               int serverPort = req.getServerPort();
+               String protocol = serverPort == 443 || req.isSecure() ? "https" : "http";
+
+               Writer writer = resp.getWriter();
+               writer.append("<?xml version='1.0' encoding='UTF-8' ?>");
+               writer.append("<xforms xmlns=\"http://openrosa.org/xforms/xformsList\">");
+               for (OdkForm form : odkForms) {
+                       StringBuilder sb = new StringBuilder();
+                       sb.append("<xform>");
+                       sb.append("<formID>" + form.getFormId() + "</formID>");
+                       sb.append("<name>" + form.getName() + "</name>");
+                       sb.append("<version>" + form.getVersion() + "</version>");
+                       sb.append("<hash>" + form.getHash(null) + "</hash>");
+                       sb.append("<descriptionText>" + form.getDescription() + "</descriptionText>");
+                       sb.append("<downloadUrl>" + protocol + "://" + serverName
+                                       + (serverPort == 80
+                                                       || serverPort == 443 ? "" : ":" + serverPort) + "/api/odk/" + form.getFileName()
+                                       + "</downloadUrl>");
+                       sb.append("</xform>");
+                       String str = sb.toString();
+                       if (log.isDebugEnabled())
+                               log.debug(str);
+                       writer.append(str);
+               }
+               writer.append("</xforms>");
+       }
+
+       public void addForm(OdkForm odkForm) {
+               odkForms.add(odkForm);
+       }
+
+       public void removeForm(OdkForm odkForm) {
+               odkForms.remove(odkForm);
+       }
+}
diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkFormServlet.java
new file mode 100644 (file)
index 0000000..3746da8
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.support.odk.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FilenameUtils;
+import org.argeo.support.odk.OdkForm;
+
+/** Retrieves a single form. */
+public class OdkFormServlet extends HttpServlet {
+       private static final long serialVersionUID = 7838305967987687370L;
+
+       private Map<String, OdkForm> odkForms = Collections.synchronizedMap(new HashMap<>());
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               resp.setContentType("text/xml; charset=utf-8");
+
+               String path = req.getServletPath();
+               String fileName = FilenameUtils.getName(path);
+               OdkForm form = odkForms.get(fileName);
+               if (form == null)
+                       throw new IllegalArgumentException("No form named " + fileName + " was found");
+
+               byte[] buffer = new byte[1024];
+               try (InputStream in = form.openStream(); OutputStream out = resp.getOutputStream();) {
+                       int bytesRead;
+                       while ((bytesRead = in.read(buffer)) != -1)
+                               out.write(buffer, 0, bytesRead);
+               }
+       }
+
+       public void addForm(OdkForm odkForm) {
+               odkForms.put(odkForm.getFileName(), odkForm);
+       }
+
+       public void removeForm(OdkForm odkForm) {
+               odkForms.remove(odkForm.getFileName());
+       }
+
+}
diff --git a/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java b/knowledge/org.argeo.support.odk/src/org/argeo/support/odk/servlet/OdkSubmissionServlet.java
new file mode 100644 (file)
index 0000000..3a97580
--- /dev/null
@@ -0,0 +1,61 @@
+package org.argeo.support.odk.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/** Receives a form submission. */
+public class OdkSubmissionServlet extends HttpServlet {
+       private static final long serialVersionUID = 7834401404691302385L;
+       private final static Log log = LogFactory.getLog(OdkSubmissionServlet.class);
+
+       private final static String XML_SUBMISSION_FILE = "xml_submission_file";
+
+       @Override
+       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               for (Part part : req.getParts()) {
+                       if (log.isDebugEnabled())
+                               log.debug("Part: " + part.getName() + ", " + part.getContentType());
+               }
+               Part xmlSubmissionPart = req.getPart(XML_SUBMISSION_FILE);
+               if (xmlSubmissionPart == null)
+                       throw new ServletException("No " + XML_SUBMISSION_FILE + " part");
+               try (InputStream in = xmlSubmissionPart.getInputStream();) {
+                       // pretty print
+                       Transformer transformer = TransformerFactory.newInstance().newTransformer();
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+                       transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+                       StreamResult result = new StreamResult(new StringWriter());
+                       StreamSource source = new StreamSource(in);
+                       transformer.transform(source, result);
+                       String xmlString = result.getWriter().toString();
+                       System.out.println(xmlString);
+               } catch (TransformerException e) {
+                       e.printStackTrace();
+               }
+
+               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);
+               resp.setStatus(201);
+               resp.getWriter().write("<OpenRosaResponse xmlns=\"http://openrosa.org/http/response\">"
+                               + "<message>Form Received!</message>" + "</OpenRosaResponse>");
+
+       }
+}
diff --git a/knowledge/pom.xml b/knowledge/pom.xml
new file mode 100644 (file)
index 0000000..b709290
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.suite</groupId>
+               <artifactId>argeo-suite</artifactId>
+               <version>2.1.16-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>knowledge</artifactId>
+       <name>Argeo Knowledge components</name>
+       <packaging>pom</packaging>
+       <modules>
+               <module>org.argeo.support.odk</module>
+       </modules>
+</project>
diff --git a/pom.xml b/pom.xml
index 6859da9b1541a0a7d8579bd940349d7854ca710a..eeafc8d989c630590028bf280f7dd0dfd882c6cd 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                <module>org.argeo.suite.ui.rap</module>
                <module>org.argeo.suite.theme.default</module>
 
+               <!-- Functional areas -->
+               <module>knowledge</module>
+
+               <!-- Packaging -->
                <module>dep</module>
                <module>dist</module>
                <module>lib</module>