1 package org
.argeo
.app
.jcr
.odk
;
3 import java
.io
.ByteArrayOutputStream
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
7 import java
.net
.URISyntaxException
;
8 import java
.nio
.charset
.StandardCharsets
;
10 import javax
.jcr
.ImportUUIDBehavior
;
11 import javax
.jcr
.Node
;
12 import javax
.jcr
.NodeIterator
;
13 import javax
.jcr
.Property
;
14 import javax
.jcr
.RepositoryException
;
15 import javax
.jcr
.Session
;
16 import javax
.jcr
.nodetype
.NodeType
;
18 import org
.argeo
.api
.cms
.CmsLog
;
19 import org
.argeo
.app
.api
.EntityMimeType
;
20 import org
.argeo
.app
.api
.EntityType
;
21 import org
.argeo
.app
.odk
.OdkNames
;
22 import org
.argeo
.app
.odk
.OrxListName
;
23 import org
.argeo
.app
.odk
.OrxManifestName
;
24 import org
.argeo
.cms
.util
.DigestUtils
;
25 import org
.argeo
.jcr
.Jcr
;
26 import org
.argeo
.jcr
.JcrUtils
;
27 import org
.argeo
.jcr
.JcrxApi
;
29 /** Utilities around ODK. */
30 public class OdkJcrUtils
{
31 private final static CmsLog log
= CmsLog
.getLog(OdkJcrUtils
.class);
33 public static Node
loadOdkForm(Node formBase
, String name
, InputStream in
, InputStream
... additionalNodes
)
34 throws RepositoryException
, IOException
{
35 if (!formBase
.isNodeType(EntityType
.formSet
.get()))
36 throw new IllegalArgumentException(
37 "Parent path " + formBase
+ " must be of type " + EntityType
.formSet
.get());
38 Node form
= JcrUtils
.getOrAdd(formBase
, name
, OrxListName
.xform
.get(), NodeType
.MIX_VERSIONABLE
);
40 String previousCsum
= JcrxApi
.getChecksum(form
, JcrxApi
.MD5
);
41 String previousFormId
= Jcr
.get(form
, OrxListName
.formID
.get());
42 String previousFormVersion
= Jcr
.get(form
, OrxListName
.version
.get());
44 Session s
= formBase
.getSession();
45 s
.importXML(form
.getPath(), in
, ImportUUIDBehavior
.IMPORT_UUID_COLLISION_REPLACE_EXISTING
);
47 for (InputStream additionalIn
: additionalNodes
) {
48 s
.importXML(form
.getPath(), additionalIn
, ImportUUIDBehavior
.IMPORT_UUID_COLLISION_REPLACE_EXISTING
);
53 // NodeIterator instances =
54 // form.getNodes("h:html/h:head/xforms:model/xforms:instance");
55 NodeIterator instances
= form
.getNode("h:html/h:head/xforms:model").getNodes("xforms:instance");
56 Node primaryInstance
= null;
57 while (instances
.hasNext()) {
58 Node instance
= instances
.nextNode();
59 if (primaryInstance
== null) {
60 primaryInstance
= instance
;
61 } else {// secondary instances
62 String instanceId
= instance
.getProperty("id").getString();
63 URI instanceUri
= null;
64 if (instance
.hasProperty("src"))
66 instanceUri
= new URI(instance
.getProperty("src").getString());
67 } catch (URISyntaxException e
) {
68 throw new IllegalArgumentException("Instance " + instanceId
+ " has a badly formatted URI", e
);
70 if (instanceUri
!= null) {
71 if ("jr".equals(instanceUri
.getScheme())) {
74 String encoding
= StandardCharsets
.UTF_8
.name();
75 String type
= instanceUri
.getHost();
76 String path
= instanceUri
.getPath();
77 if ("file".equals(type
)) {
78 if (!path
.endsWith(".xml"))
79 throw new IllegalArgumentException("File uri " + instanceUri
+ " must end with .xml");
80 // Work around bug in ODK Collect not supporting paths
81 // path = path.substring(0, path.length() - ".xml".length());
82 // Node target = file.getSession().getNode(path);
83 uuid
= path
.substring(1, path
.length() - ".xml".length());
84 mimeType
= EntityMimeType
.XML
.getMimeType();
85 } else if ("file-csv".equals(type
)) {
86 if (!path
.endsWith(".csv"))
87 throw new IllegalArgumentException("File uri " + instanceUri
+ " must end with .csv");
88 // Work around bug in ODK Collect not supporting paths
89 // path = path.substring(0, path.length() - ".csv".length());
90 // Node target = file.getSession().getNode(path);
91 uuid
= path
.substring(1, path
.length() - ".csv".length());
92 mimeType
= EntityMimeType
.CSV
.getMimeType();
94 throw new IllegalArgumentException("Unsupported instance type " + type
);
96 Node manifest
= JcrUtils
.getOrAdd(form
, OrxManifestName
.manifest
.name(),
97 OrxManifestName
.manifest
.get());
98 Node file
= JcrUtils
.getOrAdd(manifest
, instanceId
);
99 file
.addMixin(NodeType
.MIX_MIMETYPE
);
100 file
.setProperty(Property
.JCR_MIMETYPE
, mimeType
);
101 file
.setProperty(Property
.JCR_ENCODING
, encoding
);
102 Node target
= file
.getSession().getNodeByIdentifier(uuid
);
104 // if (target.isNodeType(NodeType.NT_QUERY)) {
105 // Query query = target.getSession().getWorkspace().getQueryManager().getQuery(target);
106 // query.setLimit(10);
107 // QueryResult queryResult = query.execute();
108 // RowIterator rit = queryResult.getRows();
109 // while (rit.hasNext()) {
110 // Row row = rit.nextRow();
111 // for (Value value : row.getValues()) {
112 // System.out.print(value.getString());
113 // System.out.print(',');
115 // System.out.print('\n');
120 if (target
.isNodeType(NodeType
.MIX_REFERENCEABLE
)) {
121 file
.setProperty(Property
.JCR_ID
, target
);
122 if (file
.hasProperty(Property
.JCR_PATH
))
123 file
.getProperty(Property
.JCR_PATH
).remove();
125 file
.setProperty(Property
.JCR_PATH
, target
.getPath());
126 if (file
.hasProperty(Property
.JCR_ID
))
127 file
.getProperty(Property
.JCR_ID
).remove();
134 if (primaryInstance
== null)
135 throw new IllegalArgumentException("No primary instance found in " + form
);
136 if (!primaryInstance
.hasNodes())
137 throw new IllegalArgumentException("No data found in primary instance of " + form
);
138 NodeIterator primaryInstanceChildren
= primaryInstance
.getNodes();
139 Node data
= primaryInstanceChildren
.nextNode();
140 if (primaryInstanceChildren
.hasNext())
141 throw new IllegalArgumentException("More than one data found in primary instance of " + form
);
142 String formId
= data
.getProperty("id").getString();
143 if (previousFormId
!= null && !formId
.equals(previousFormId
))
144 log
.warn("Form id of " + form
+ " changed from " + previousFormId
+ " to " + formId
);
145 form
.setProperty(OrxListName
.formID
.get(), formId
);
146 String formVersion
= data
.getProperty("version").getString();
148 if (previousCsum
== null)// save before checksuming
151 try (ByteArrayOutputStream out
= new ByteArrayOutputStream()) {
152 s
.exportDocumentView(form
.getPath() + "/" + OdkNames
.H_HTML
, out
, true, false);
153 newCsum
= DigestUtils
.digest(DigestUtils
.MD5
, out
.toByteArray());
155 if (previousCsum
== null) {
156 JcrxApi
.addChecksum(form
, newCsum
);
157 JcrUtils
.updateLastModified(form
);
158 form
.setProperty(OrxListName
.version
.get(), formVersion
);
160 s
.getWorkspace().getVersionManager().checkpoint(form
.getPath());
161 if (log
.isDebugEnabled())
162 log
.debug("New form " + form
);
164 if (newCsum
.equals(previousCsum
)) {
167 if (log
.isDebugEnabled())
168 log
.debug("Unmodified form " + form
);
171 if (formVersion
.equals(previousFormVersion
)) {
173 throw new IllegalArgumentException("Form " + form
+ " has been changed but version " + formVersion
174 + " has not been changed, discarding changes...");
176 form
.setProperty(OrxListName
.version
.get(), formVersion
);
177 JcrxApi
.addChecksum(form
, newCsum
);
178 JcrUtils
.updateLastModified(form
);
180 s
.getWorkspace().getVersionManager().checkpoint(form
.getPath());
181 if (log
.isDebugEnabled()) {
182 log
.debug("Updated form " + form
);
183 log
.debug("Previous csum " + previousCsum
);
184 log
.debug("New csum " + newCsum
);
192 private OdkJcrUtils() {