1 package org.collectionspace.services.batch.nuxeo;
4 import java.net.URISyntaxException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collections;
8 import java.util.HashMap;
11 import javax.ws.rs.core.PathSegment;
12 import javax.ws.rs.core.UriInfo;
13 import org.collectionspace.services.batch.AbstractBatchInvocable;
14 import org.collectionspace.services.client.CollectionObjectClient;
15 import org.collectionspace.services.client.MovementClient;
16 import org.collectionspace.services.client.PayloadOutputPart;
17 import org.collectionspace.services.client.PoxPayloadOut;
18 import org.collectionspace.services.client.RelationClient;
19 import org.collectionspace.services.common.ResourceBase;
20 import org.collectionspace.services.common.ResourceMap;
21 import org.collectionspace.services.common.api.Tools;
22 import org.collectionspace.services.common.invocable.InvocationResults;
23 import org.collectionspace.services.jaxb.AbstractCommonList;
24 import org.collectionspace.services.movement.nuxeo.MovementConstants;
25 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
26 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
28 import org.dom4j.DocumentException;
29 import org.dom4j.DocumentHelper;
30 import org.dom4j.Element;
31 import org.dom4j.Node;
32 import org.dom4j.XPath;
33 import org.jboss.resteasy.specimpl.UriInfoImpl;
34 import org.nuxeo.ecm.core.api.DocumentModel;
35 import org.nuxeo.ecm.core.api.DocumentModelList;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
41 // FIXME; Get from existing constants and replace these local declarations
42 final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
43 final static String OBJECT_NUMBER_FIELD_NAME = "objectNumber";
44 private final static String RELATIONS_COMMON_SCHEMA = "relations_common"; // FIXME: Get from external constant
45 private final static String RELATION_DOCTYPE = "Relation"; // FIXME: Get from external constant
46 private final static String SUBJECT_CSID_PROPERTY = "subjectCsid"; // FIXME: Get from external constant
47 private final static String OBJECT_CSID_PROPERTY = "objectCsid"; // FIXME: Get from external constant
48 private final static String SUBJECT_DOCTYPE_PROPERTY = "subjectDocumentType"; // FIXME: Get from external constant
49 private final static String OBJECT_DOCTYPE_PROPERTY = "objectDocumentType"; // FIXME: Get from external constant
50 protected final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common"; // FIXME: Get from external constant
51 private final static String COLLECTIONOBJECT_DOCTYPE = "CollectionObject"; // FIXME: Get from external constant
52 protected final static String COMPUTED_CURRENT_LOCATION_PROPERTY = "computedCurrentLocation"; // FIXME: Create and then get from external constant
53 protected final static String MOVEMENTS_COMMON_SCHEMA = "movements_common"; // FIXME: Get from external constant
54 private final static String MOVEMENT_DOCTYPE = MovementConstants.NUXEO_DOCTYPE;
55 private InvocationResults results = new InvocationResults();
56 final String CLASSNAME = this.getClass().getSimpleName();
57 final Logger logger = LoggerFactory.getLogger(UpdateObjectLocationBatchJob.class);
59 // Initialization tasks
60 public UpdateObjectLocationBatchJob() {
61 setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST));
65 * The main work logic of the batch job. Will be called after setContext.
70 setCompletionStatus(STATUS_MIN_PROGRESS);
73 // FIXME: Placeholder during early development
74 if (logger.isInfoEnabled()) {
75 logger.info("Invoking " + CLASSNAME + " ...");
76 logger.info("Invocation context is: " + getInvocationContext().getMode());
79 if (!requestedInvocationModeIsSupported()) {
80 setInvocationModeNotSupportedResult();
83 List<String> csids = new ArrayList<String>();
84 if (requestIsForInvocationModeSingle()) {
85 String singleCsid = getInvocationContext().getSingleCSID();
86 if (Tools.notBlank(singleCsid)) {
87 csids.add(singleCsid);
89 } else if (requestIsForInvocationModeList()) {
90 List<String> listCsids = (getInvocationContext().getListCSIDs().getCsid());
91 if (listCsids == null || listCsids.isEmpty()) {
92 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
94 csids.addAll(listCsids);
95 } else if (requestIsForInvocationModeGroup()) {
96 String groupCsid = getInvocationContext().getGroupCSID();
97 // FIXME: Get individual CSIDs from the group
98 // and add them to the list
101 if (csids.isEmpty()) {
102 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
105 // Update the current computed location field for each CollectionObject
106 setResults(updateComputedCurrentLocations(csids));
107 setCompletionStatus(STATUS_COMPLETE);
109 } catch (Exception e) {
110 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
111 setErrorResult(errMsg);
116 // Ray's convenience methods from his AbstractBatchJob class for the UC Berkeley Botanical Garden v2.4 implementation.
117 protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
118 ResourceBase resource = getResourceMap().get(serviceName);
119 return findByCsid(resource, csid);
122 protected PoxPayloadOut findByCsid(ResourceBase resource, String csid) throws URISyntaxException, DocumentException {
123 byte[] response = resource.get(null, createUriInfo(), csid);
124 PoxPayloadOut payload = new PoxPayloadOut(response);
128 protected UriInfo createUriInfo() throws URISyntaxException {
129 return createUriInfo("");
132 protected UriInfo createUriInfo(String queryString) throws URISyntaxException {
133 URI absolutePath = new URI("");
134 URI baseUri = new URI("");
135 return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
138 protected UriInfo createRelationSearchUriInfo(String subjectCsid, String objType) throws URISyntaxException {
139 String queryString = "sbj=" + subjectCsid + "&objType=" + objType;
140 URI uri = new URI(null, null, null, queryString, null);
141 return createUriInfo(uri.getRawQuery());
145 * Get a field value from a PoxPayloadOut, given a part name and
146 * namespace-qualified xpath expression.
148 protected String getFieldValue(PoxPayloadOut payload, String partLabel, String namespacePrefix, String namespace, String fieldPath) {
150 PayloadOutputPart part = payload.getPart(partLabel);
153 Element element = part.asElement();
154 logger.info(partLabel + " part element =" + element.asXML());
156 Map<String, String> namespaceUris = new HashMap<String, String>();
157 namespaceUris.put(namespacePrefix, namespace);
159 XPath xPath = DocumentHelper.createXPath(fieldPath);
160 xPath.setNamespaceURIs(namespaceUris);
162 Node node = xPath.selectSingleNode(element);
163 // Node node = element.selectSingleNode(fieldPath);
166 value = node.getText();
173 protected List<String> getFieldValues(PoxPayloadOut payload, String partLabel, String fieldPath) {
174 List<String> values = new ArrayList<String>();
175 PayloadOutputPart part = payload.getPart(partLabel);
178 Element element = part.asElement();
179 List<Node> nodes = element.selectNodes(fieldPath);
182 for (Node node : nodes) {
183 values.add(node.getText());
191 private InvocationResults updateComputedCurrentLocations(List<String> csids) {
193 ResourceMap resourcemap = getResourceMap();
194 ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
195 ResourceBase movementResource = resourcemap.get(MovementClient.SERVICE_NAME);
196 PoxPayloadOut collectionObjectPayload;
198 String computedCurrentLocation;
200 // FIXME: Temporary during testing/development
201 final String COMPUTED_CURRENT_LOCATION = "FOO_COMPUTED_CURRENT_LOCATION";
205 // For each CollectionObject record:
206 for (String csid : csids) {
208 // Get the movement records related to this record
210 // FIXME: Create a convenience method for constructing queries like the following
211 String queryString = "rtObj=" + csid;
212 URI uri = new URI(null, null, null, queryString, null);
213 UriInfo uriInfo = createUriInfo(uri.getRawQuery());
215 AbstractCommonList relatedMovements = movementResource.getList(uriInfo);
216 if (logger.isInfoEnabled()) {
217 logger.info("Identified " + relatedMovements.getTotalItems()
218 + " movement records related to CollectionObject record " + csid);
220 if (relatedMovements.getTotalItems() == 0) {
225 * Query resulting from the above:
226 * Executing CMIS query: SELECT DOC.nuxeo:pathSegment, DOC.dc:title,
227 * REL.dc:title, REL.relations_common:objectCsid, REL.relations_common:subjectCsid
228 * FROM Movement DOC JOIN Relation REL ON REL.relations_common:subjectCsid = DOC.nuxeo:pathSegment
229 * WHERE REL.relations_common:objectCsid = 'c0bdd018-01c1-412a-bc21' AND
230 * DOC.nuxeo:isVersion = false ORDER BY DOC.collectionspace_core:updatedAt
233 // FIXME: Get the reciprocal relation records, via rtSbj=, as well,
234 // and remove duplicates
236 // FIXME Temporary for testing
237 queryString = "rtSbj=" + csid;
238 uri = new URI(null, null, null, queryString, null);
239 uriInfo = createUriInfo(uri.getRawQuery());
241 relatedMovements = movementResource.getList(uriInfo);
242 if (logger.isInfoEnabled()) {
243 logger.info("Identified " + relatedMovements.getTotalItems()
244 + " movement records related to CollectionObject record " + csid);
246 if (relatedMovements.getTotalItems() == 0) {
251 * Query resulting from the above:
252 * Executing CMIS query: SELECT DOC.nuxeo:pathSegment, DOC.dc:title,
253 * REL.dc:title, REL.relations_common:objectCsid, REL.relations_common:subjectCsid
254 * FROM Movement DOC JOIN Relation REL ON REL.relations_common:objectCsid = DOC.nuxeo:pathSegment
255 * WHERE REL.relations_common:subjectCsid = '7db3c206-3a3c-4f5c-8155' AND
256 * DOC.nuxeo:isVersion = false ORDER BY DOC.collectionspace_core:updatedAt DESC
261 // FIXME: Similar to RelationsUtils.buildWhereClause()
262 // We might consider adding a 'bidirectional where clause' like the following there.
264 String query = String.format(
265 "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = "
267 + " (%2$s:subjectCsid = '%3$s' "
268 + " AND %2$s:objectDocumentType = '%4$s') "
270 + " (%2$s:objectCsid = '%3$s' "
271 + " AND %2$s:subjectDocumentType = '%4$s') "
273 + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
274 RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, csid, MOVEMENT_DOCTYPE);
276 relationResource.getList(uriInfo);
277 query = NuxeoUtils.buildNXQLQuery(csids, null);
278 DocumentModelList relationDocModels = coreSession.query(query);
281 // Get the latest movement record from among those
283 // Extract its current location value
285 // FIXME: Temporary during testing/development
286 computedCurrentLocation = COMPUTED_CURRENT_LOCATION;
288 // Update the computed current location value in the CollectionObject record
289 collectionObjectPayload = findByCsid(collectionObjectResource, csid);
290 if (Tools.notBlank(collectionObjectPayload.toXML())) {
291 if (logger.isInfoEnabled()) {
292 logger.info("Payload: " + "\n" + collectionObjectPayload);
294 // Silently fails at various places in dom4j calls (selectSingleNode, selectNode,
295 // createXpath) in any of the methods tried above, without throwing an Exception
297 objectNumber = getFieldValue(collectionObjectPayload,
298 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME,
299 "ns2", "http://collectionspace.org/services/collectionobject",
300 OBJECT_NUMBER_FIELD_NAME);
301 if (logger.isInfoEnabled()) {
302 logger.info("Object number: " + objectNumber);
305 objectNumber = "BAR"; // FIXME
306 if (Tools.notBlank(objectNumber)) {
307 String collectionObjectUpdatePayload =
308 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
309 + "<document name=\"collectionobject\">"
310 + "<ns2:collectionobjects_common xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
311 + "<objectNumber>" + objectNumber + "</objectNumber>"
312 + "<computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
313 + "</ns2:collectionobjects_common></document>";
314 if (logger.isInfoEnabled()) {
315 logger.info("Update payload: " + "\n" + collectionObjectUpdatePayload);
317 byte[] response = collectionObjectResource.update(resourcemap, null, csid, collectionObjectUpdatePayload);
319 if (logger.isTraceEnabled()) {
320 logger.trace("Computed current location value for CollectionObject " + csid + " set to " + computedCurrentLocation);
327 } catch (Exception e) {
328 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
329 errMsg = errMsg + "Successfully updated " + numAffected + " CollectionObject record(s) prior to error.";
330 setErrorResult(errMsg);
331 getResults().setNumAffected(numAffected);
336 .setNumAffected(numAffected);