--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ODK Manifest Servlet">
+ <implementation class="org.argeo.support.odk.servlet.OdkManifestServlet"/>
+ <service>
+ <provide interface="javax.servlet.Servlet"/>
+ </service>
+ <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/formManifest/*"/>
+ <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=odkServletContext)"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=odk)"/>
+</scr:component>
OSGI-INF/odkServletContext.xml,\
OSGI-INF/odkFormListServlet.xml,\
OSGI-INF/odkFormServlet.xml,\
-OSGI-INF/odkSubmissionServlet.xml
+OSGI-INF/odkSubmissionServlet.xml,\
+OSGI-INF/odkManifestServlet.xml
Import-Package:\
org.osgi.service.http.context,\
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.entity.EntityType;
+import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrUtils;
import org.argeo.jcr.JcrxApi;
import org.argeo.util.DigestUtils;
if (!formBase.isNodeType(EntityType.formSet.get()))
throw new IllegalArgumentException(
"Parent path " + formBase + " must be of type " + EntityType.formSet.get());
- Node form = JcrUtils.getOrAdd(formBase, name, OrxListType.xform.get(), NodeType.MIX_SIMPLE_VERSIONABLE);
+ Node form = JcrUtils.getOrAdd(formBase, name, OrxListName.xform.get(), NodeType.MIX_VERSIONABLE);
String previousCsum = JcrxApi.getChecksum(form, JcrxApi.MD5);
+ String previousFormId = Jcr.get(form, OrxListName.formID.get());
+ String previousFormVersion = Jcr.get(form, OrxListName.version.get());
Session s = formBase.getSession();
// String res = "/odk/apafSession.odk.xml";
s.importXML(form.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
// }
+ // manage instances
+ // NodeIterator instances =
+ // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
+ NodeIterator instances = form.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
+ Node primaryInstance = null;
+ while (instances.hasNext()) {
+ Node instance = instances.nextNode();
+ if (primaryInstance == null) {
+ primaryInstance = instance;
+ } else {// secondary instances
+ String instanceId = instance.getProperty("id").getString();
+ URI instanceUri = null;
+ if (instance.hasProperty("src"))
+ try {
+ instanceUri = new URI(instance.getProperty("src").getString());
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Instance " + instanceId + " has a badly formatted URI", e);
+ }
+ if (instanceUri != null) {
+ if ("jr".equals(instanceUri.getScheme())) {
+ String type = instanceUri.getHost();
+ if ("file".equals(type)) {
+ Node manifest = JcrUtils.getOrAdd(form, OrxManifestName.manifest.name(),
+ OrxManifestName.manifest.get());
+ Node file = JcrUtils.getOrAdd(manifest, instanceId);
+ String path = instanceUri.getPath();
+ if (!path.endsWith(".xml"))
+ throw new IllegalArgumentException("File uri " + instanceUri + " must end with .xml");
+ path = path.substring(0, path.length() - ".xml".length());
+ Node target = file.getSession().getNode(path);
+ if (target.isNodeType(NodeType.MIX_REFERENCEABLE)) {
+ file.setProperty(Property.JCR_ID, target);
+ if (file.hasProperty(Property.JCR_PATH))
+ file.getProperty(Property.JCR_PATH).remove();
+ } else {
+ file.setProperty(Property.JCR_PATH, target.getPath());
+ if (file.hasProperty(Property.JCR_ID))
+ file.getProperty(Property.JCR_ID).remove();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (primaryInstance == null)
+ throw new IllegalArgumentException("No primary instance found in " + form);
+ if (!primaryInstance.hasNodes())
+ throw new IllegalArgumentException("No data found in primary instance of " + form);
+ NodeIterator primaryInstanceChildren = primaryInstance.getNodes();
+ Node data = primaryInstanceChildren.nextNode();
+ if (primaryInstanceChildren.hasNext())
+ throw new IllegalArgumentException("More than one data found in primary instance of " + form);
+ String formId = data.getProperty("id").getString();
+ if (previousFormId != null && !formId.equals(previousFormId))
+ log.warn("Form id of " + form + " changed from " + previousFormId + " to " + formId);
+ form.setProperty(OrxListName.formID.get(), formId);
+ String formVersion = data.getProperty("version").getString();
+
if (previousCsum == null)// save before checksuming
s.save();
String newCsum;
if (previousCsum == null) {
JcrxApi.addChecksum(form, newCsum);
JcrUtils.updateLastModified(form);
+ form.setProperty(OrxListName.version.get(), formVersion);
s.save();
s.getWorkspace().getVersionManager().checkpoint(form.getPath());
if (log.isDebugEnabled())
log.debug("Unmodified form " + form);
return;
} else {
+ if (formVersion.equals(previousFormVersion)) {
+ s.refresh(false);
+ throw new IllegalArgumentException("Form " + form + " has been changed but version " + formVersion
+ + " has not been changed, discarding changes...");
+ }
+ form.setProperty(OrxListName.version.get(), formVersion);
JcrxApi.addChecksum(form, newCsum);
JcrUtils.updateLastModified(form);
s.save();
--- /dev/null
+package org.argeo.support.odk;
+
+import org.argeo.entity.JcrName;
+
+/** Types related to the http://openrosa.org/xforms/xformsList namespace. */
+public enum OrxListName implements JcrName {
+ xform,
+ // names
+ formID, version;
+
+ @Override
+ public String getPrefix() {
+ return prefix();
+ }
+
+ public static String prefix() {
+ return "orxList";
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace();
+ }
+
+ public static String namespace() {
+ return "http://openrosa.org/xforms/xformsList";
+ }
+
+}
+++ /dev/null
-package org.argeo.support.odk;
-
-import org.argeo.entity.JcrName;
-
-/** Types related to the http://openrosa.org/xforms/xformsList namespace. */
-public enum OrxListType implements JcrName {
- xform;
-
- @Override
- public String getPrefix() {
- return prefix();
- }
-
- public static String prefix() {
- return "orxList";
- }
-
- @Override
- public String getNamespace() {
- return namespace();
- }
-
- public static String namespace() {
- return "http://openrosa.org/xforms/xformsList";
- }
-
-}
--- /dev/null
+package org.argeo.support.odk;
+
+import org.argeo.entity.JcrName;
+
+/** Types related to the http://openrosa.org/xforms/xformsList namespace. */
+public enum OrxManifestName implements JcrName {
+ manifest, mediaFile;
+
+ @Override
+ public String getPrefix() {
+ return prefix();
+ }
+
+ public static String prefix() {
+ return "orxManifest";
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace();
+ }
+
+ public static String namespace() {
+ return "http://openrosa.org/xforms/xformsManifest";
+ }
+
+}
// OpenRosa web API
[orxList:xform] > mix:created, mix:lastModified, jcrx:csum, entity:form
+- orxList:formID (STRING)
+- orxList:version (STRING)
+ h:html (odk:html) = odk:html
++ manifest (orxManifest:manifest) = orxManifest:manifest
+
+[orxManifest:manifest]
++ * (orxManifest:mediaFile) = orxManifest:mediaFile
+
+[orxManifest:mediaFile] > nt:address, jcrx:csum
[orx:submission] > mix:created, entity:formSubmission
+ xml_submission_file (nt:unstructured) = nt:unstructured
import java.io.IOException;
import java.io.Writer;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.argeo.cms.servlet.ServletAuthUtils;
import org.argeo.entity.EntityType;
import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
import org.argeo.jcr.JcrxApi;
import org.argeo.support.odk.OdkForm;
-import org.argeo.support.odk.OdkNames;
-import org.argeo.support.odk.OrxListType;
+import org.argeo.support.odk.OrxListName;
+import org.argeo.support.odk.OrxManifestName;
/** Lists available forms. */
public class OdkFormListServlet extends HttpServlet {
private Set<OdkForm> odkForms = Collections.synchronizedSet(new HashSet<>());
- private DateTimeFormatter versionFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmm")
- .withZone(ZoneId.from(ZoneOffset.UTC));
+// private DateTimeFormatter versionFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd-HHmm")
+// .withZone(ZoneId.from(ZoneOffset.UTC));
private Repository repository;
// query = session.getWorkspace().getQueryManager()
// .createQuery("SELECT * FROM [nt:unstructured]", Query.JCR_SQL2);
query = session.getWorkspace().getQueryManager()
- .createQuery("SELECT * FROM [" + OrxListType.xform.get() + "]", Query.JCR_SQL2);
+ .createQuery("SELECT * FROM [" + OrxListName.xform.get() + "]", Query.JCR_SQL2);
} else {
query = session.getWorkspace().getQueryManager()
.createQuery(
- "SELECT node FROM [" + OrxListType.xform.get()
+ "SELECT node FROM [" + OrxListName.xform.get()
+ "] AS node WHERE ISDESCENDANTNODE (node, '" + pathInfo + "')",
Query.JCR_SQL2);
}
while (nit.hasNext()) {
StringBuilder sb = new StringBuilder();
Node node = nit.nextNode();
- if (node.isNodeType(OrxListType.xform.get())) {
+ if (node.isNodeType(OrxListName.xform.get())) {
sb.append("<xform>");
- sb.append("<formID>"
- + node.getNode(OdkNames.H_HTML + "/h:head/xforms:model/xforms:instance/xforms:data")
- .getProperty("id").getString()
- + "</formID>");
+ sb.append("<formID>" + node.getProperty(OrxListName.formID.get()).getString() + "</formID>");
sb.append("<name>" + Jcr.getTitle(node) + "</name>");
- sb.append("<version>" + versionFormatter.format(JcrUtils.getModified(node)) + "</version>");
-// sb.append("<version>" + versionFormatter.format(JcrUtils.getModified(node)) + "</version>");
+ sb.append("<version>" + node.getProperty(OrxListName.version.get()).getString() + "</version>");
sb.append("<hash>md5:" + JcrxApi.getChecksum(node, JcrxApi.MD5) + "</hash>");
if (node.hasProperty(Property.JCR_DESCRIPTION))
sb.append("<name>" + node.getProperty(Property.JCR_DESCRIPTION).getString() + "</name>");
sb.append("<downloadUrl>" + protocol + "://" + serverName
+ (serverPort == 80 || serverPort == 443 ? "" : ":" + serverPort) + "/api/odk/form/"
+ node.getPath() + "</downloadUrl>");
+ if (node.hasNode(OrxManifestName.manifest.name())) {
+ sb.append("<manifestUrl>" + protocol + "://" + serverName
+ + (serverPort == 80 || serverPort == 443 ? "" : ":" + serverPort)
+ + "/api/odk/formManifest" + node.getNode(OrxManifestName.manifest.name()).getPath()
+ + "</manifestUrl>");
+ }
sb.append("</xform>");
} else if (node.isNodeType(EntityType.formSet.get())) {
sb.append("<xforms-group>");
--- /dev/null
+package org.argeo.support.odk.servlet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.servlet.ServletAuthUtils;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.support.odk.OrxManifestName;
+import org.argeo.util.DigestUtils;
+
+/** Describe additional files. */
+public class OdkManifestServlet extends HttpServlet {
+ private static final long serialVersionUID = 138030510865877478L;
+
+ private Repository repository;
+
+ @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 pathInfo = req.getPathInfo();
+ if (pathInfo.startsWith("//"))
+ pathInfo = pathInfo.substring(1);
+
+ String serverName = req.getServerName();
+ int serverPort = req.getServerPort();
+ String protocol = serverPort == 443 || req.isSecure() ? "https" : "http";
+
+ Session session = ServletAuthUtils.doAs(() -> Jcr.login(repository, null), req);
+
+ try {
+ Node node = session.getNode(pathInfo);
+ if (node.isNodeType(OrxManifestName.manifest.get())) {
+ Writer writer = resp.getWriter();
+ writer.append("<?xml version='1.0' encoding='UTF-8' ?>");
+ writer.append("<manifest xmlns=\"http://openrosa.org/xforms/xformsManifest\">");
+ NodeIterator nit = node.getNodes();
+ while (nit.hasNext()) {
+ Node file = nit.nextNode();
+ if (file.isNodeType(OrxManifestName.mediaFile.get())) {
+ writer.append("<mediaFile>");
+
+ if (file.isNodeType(NodeType.NT_ADDRESS)) {
+ Node target = file.getProperty(Property.JCR_ID).getNode();
+ writer.append("<filename>");
+ writer.append(target.getPath().substring(1) + ".xml");
+ writer.append("</filename>");
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ session.exportDocumentView(target.getPath(), out, true, false);
+ String fileCsum = DigestUtils.digest(DigestUtils.MD5, out.toByteArray());
+// JcrxApi.addChecksum(file, fileCsum);
+ writer.append("<hash>");
+ writer.append("md5sum:" + fileCsum);
+ writer.append("</hash>");
+ }
+ writer.append("<downloadUrl>" + protocol + "://" + serverName
+ + (serverPort == 80 || serverPort == 443 ? "" : ":" + serverPort)
+ + "/api/odk/formManifest" + file.getPath() + "</downloadUrl>");
+ }
+ writer.append("</mediaFile>");
+ }
+ }
+
+ writer.append("</manifest>");
+ } else if (node.isNodeType(OrxManifestName.mediaFile.get())) {
+ if (node.isNodeType(NodeType.NT_ADDRESS)) {
+ Node target = node.getProperty(Property.JCR_ID).getNode();
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ session.exportDocumentView(target.getPath(), out, true, false);
+ System.out.println(new String(out.toByteArray(), StandardCharsets.UTF_8));
+ resp.getOutputStream().write(out.toByteArray());
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported node " + node);
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported node " + node);
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException(e);
+ } finally {
+ Jcr.logout(session);
+ }
+
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+}
// see https://www.w3.org/2003/01/geo/
//<geo = "http://www.w3.org/2003/01/geo/wgs84_pos#">
-<ldap = "http://www.argeo.org/ns/ldap">
+<ldif = "http://www.argeo.org/ns/ldif">
<entity = 'http://www.argeo.org/ns/entity'>
[entity:entity] > mix:created, mix:referenceable
// A real person
[entity:person] > entity:entity
mixin
-- ldap:sn (String)
-- ldap:givenName (String)
+- ldif:sn (String)
+- ldif:givenName (String)
package org.argeo.suite.ui;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
if (defaultLayers == null)
throw new IllegalArgumentException("Default layers must be set.");
if (log.isDebugEnabled())
- log.debug("Default layers: " + defaultLayers);
+ log.debug("Default layers: " + Arrays.asList(defaultLayers));
}
public void addLayer(SuiteLayer layer, Map<String, Object> properties) {