<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>
--- /dev/null
+<?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>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?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>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+
+Service-Component:\
+OSGI-INF/odkFormListServlet.xml,\
+OSGI-INF/odkFormServlet.xml,\
+OSGI-INF/odkSubmissionServlet.xml
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
+source.. = src/
--- /dev/null
+<?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>
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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();
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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());
+ }
+
+}
--- /dev/null
+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>");
+
+ }
+}
--- /dev/null
+<?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>
<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>