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.AbstractCommonListUtils;
15 import org.collectionspace.services.client.CollectionObjectClient;
16 import org.collectionspace.services.client.MovementClient;
17 import org.collectionspace.services.client.PayloadOutputPart;
18 import org.collectionspace.services.client.PoxPayloadOut;
19 import org.collectionspace.services.client.RelationClient;
20 import org.collectionspace.services.common.ResourceBase;
21 import org.collectionspace.services.common.ResourceMap;
22 import org.collectionspace.services.common.api.Tools;
23 import org.collectionspace.services.common.invocable.InvocationResults;
24 import org.collectionspace.services.jaxb.AbstractCommonList;
25 import org.collectionspace.services.movement.nuxeo.MovementConstants;
26 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
27 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
29 import org.dom4j.DocumentException;
30 import org.dom4j.DocumentHelper;
31 import org.dom4j.Element;
32 import org.dom4j.Node;
33 import org.dom4j.XPath;
34 import org.jboss.resteasy.specimpl.UriInfoImpl;
35 import org.nuxeo.ecm.core.api.DocumentModel;
36 import org.nuxeo.ecm.core.api.DocumentModelList;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
42 // FIXME: Where appropriate, get from existing constants rather than local declarations
43 private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
44 private final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common";
45 private final static String MOVEMENTS_COMMON_SCHEMA = MovementConstants.NUXEO_SCHEMA_NAME;
46 private final static String COMPUTED_CURRENT_LOCATION_ELEMENT_NAME = "computedCurrentLocation";
47 private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
48 private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
49 private InvocationResults results = new InvocationResults();
50 private final String CLASSNAME = this.getClass().getSimpleName();
51 private final Logger logger = LoggerFactory.getLogger(UpdateObjectLocationBatchJob.class);
53 // Initialization tasks
54 public UpdateObjectLocationBatchJob() {
55 setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST));
59 * The main work logic of the batch job. Will be called after setContext.
64 setCompletionStatus(STATUS_MIN_PROGRESS);
67 // FIXME: Placeholder during early development
68 if (logger.isInfoEnabled()) {
69 logger.info("Invoking " + CLASSNAME + " ...");
70 logger.info("Invocation context is: " + getInvocationContext().getMode());
73 if (!requestedInvocationModeIsSupported()) {
74 setInvocationModeNotSupportedResult();
77 List<String> csids = new ArrayList<String>();
78 if (requestIsForInvocationModeSingle()) {
79 String singleCsid = getInvocationContext().getSingleCSID();
80 if (Tools.notBlank(singleCsid)) {
81 csids.add(singleCsid);
83 } else if (requestIsForInvocationModeList()) {
84 List<String> listCsids = (getInvocationContext().getListCSIDs().getCsid());
85 if (listCsids == null || listCsids.isEmpty()) {
86 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
88 csids.addAll(listCsids);
89 } else if (requestIsForInvocationModeGroup()) {
90 String groupCsid = getInvocationContext().getGroupCSID();
91 // FIXME: Get individual CSIDs from the group
92 // and add them to the list
95 if (csids.isEmpty()) {
96 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
99 // Update the current computed location field for each CollectionObject
100 setResults(updateComputedCurrentLocations(csids));
101 setCompletionStatus(STATUS_COMPLETE);
103 } catch (Exception e) {
104 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
105 setErrorResult(errMsg);
110 // Ray's convenience methods from his AbstractBatchJob class for the UC Berkeley Botanical Garden v2.4 implementation.
111 protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
112 ResourceBase resource = getResourceMap().get(serviceName);
113 return findByCsid(resource, csid);
116 protected PoxPayloadOut findByCsid(ResourceBase resource, String csid) throws URISyntaxException, DocumentException {
117 byte[] response = resource.get(null, createUriInfo(), csid);
118 PoxPayloadOut payload = new PoxPayloadOut(response);
122 protected UriInfo createUriInfo() throws URISyntaxException {
123 return createUriInfo("");
126 protected UriInfo createUriInfo(String queryString) throws URISyntaxException {
127 URI absolutePath = new URI("");
128 URI baseUri = new URI("");
129 return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
132 protected UriInfo createRelationSearchUriInfo(String subjectCsid, String objType) throws URISyntaxException {
133 String queryString = "sbj=" + subjectCsid + "&objType=" + objType;
134 URI uri = new URI(null, null, null, queryString, null);
135 return createUriInfo(uri.getRawQuery());
139 * Get a field value from a PoxPayloadOut, given a part name and
140 * namespace-qualified xpath expression.
142 protected String getFieldValue(PoxPayloadOut payload, String partLabel, String namespacePrefix, String namespace, String fieldPath) {
144 PayloadOutputPart part = payload.getPart(partLabel);
147 Element element = part.asElement();
148 logger.info(partLabel + " part element =" + element.asXML());
150 Map<String, String> namespaceUris = new HashMap<String, String>();
151 namespaceUris.put(namespacePrefix, namespace);
153 XPath xPath = DocumentHelper.createXPath(fieldPath);
154 xPath.setNamespaceURIs(namespaceUris);
156 Node node = xPath.selectSingleNode(element);
157 // Node node = element.selectSingleNode(fieldPath);
160 value = node.getText();
167 protected List<String> getFieldValues(PoxPayloadOut payload, String partLabel, String fieldPath) {
168 List<String> values = new ArrayList<String>();
169 PayloadOutputPart part = payload.getPart(partLabel);
172 Element element = part.asElement();
173 List<Node> nodes = element.selectNodes(fieldPath);
176 for (Node node : nodes) {
177 values.add(node.getText());
185 private InvocationResults updateComputedCurrentLocations(List<String> csids) {
187 ResourceMap resourcemap = getResourceMap();
188 ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
189 ResourceBase movementResource = resourcemap.get(MovementClient.SERVICE_NAME);
190 PoxPayloadOut collectionObjectPayload;
192 String computedCurrentLocation;
194 // FIXME: Temporary during testing/development
195 final String COMPUTED_CURRENT_LOCATION = "FOO_COMPUTED_CURRENT_LOCATION";
199 // For each CollectionObject record:
200 for (String csid : csids) {
202 // Get the movement records related to this record
204 // FIXME: Create a convenience method for constructing queries like the following
205 String queryString = "rtObj=" + csid;
206 URI uri = new URI(null, null, null, queryString, null);
207 UriInfo uriInfo = createUriInfo(uri.getRawQuery());
209 AbstractCommonList relatedMovements = movementResource.getList(uriInfo);
210 if (logger.isInfoEnabled()) {
211 logger.info("Identified " + relatedMovements.getTotalItems()
212 + " movement records related to CollectionObject record " + csid);
214 if (relatedMovements.getTotalItems() == 0) {
218 // FIXME: Get the reciprocal relation records, via rtSbj=, as well,
219 // and remove duplicates
221 // FIXME Temporary for testing, until we integrate the two list results
222 queryString = "rtSbj=" + csid;
223 uri = new URI(null, null, null, queryString, null);
224 uriInfo = createUriInfo(uri.getRawQuery());
226 relatedMovements = movementResource.getList(uriInfo);
227 if (logger.isInfoEnabled()) {
228 logger.info("Identified " + relatedMovements.getTotalItems()
229 + " movement records related to CollectionObject record " + csid);
231 if (relatedMovements.getTotalItems() == 0) {
235 // Get the latest movement record from among those, and extract
236 // its current location value
237 computedCurrentLocation = "";
238 String currentLocation;
240 String mostRecentLocationDate = "";
241 for (AbstractCommonList.ListItem movementRecord : relatedMovements.getListItem()) {
242 locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, LOCATION_DATE_ELEMENT_NAME);
243 if (Tools.notBlank(locationDate)) {
244 if (logger.isInfoEnabled()) {
245 logger.info("Location date value = " + locationDate);
248 currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, COMPUTED_CURRENT_LOCATION_ELEMENT_NAME);
249 if (Tools.notBlank(currentLocation)) {
250 if (logger.isInfoEnabled()) {
251 logger.info("Current location value = " + currentLocation);
254 if (Tools.notBlank(locationDate) && Tools.notBlank(currentLocation)) {
255 // Assumes for that all values for this element/field will be
256 // ISO 8601 date values that can be ordered via string comparison.
257 // We might consider whether to first convert to date values instead.
258 if (locationDate.compareTo(mostRecentLocationDate) > 1) {
259 mostRecentLocationDate = locationDate;
260 computedCurrentLocation = currentLocation;
266 // Update the computed current location value in the CollectionObject record
267 collectionObjectPayload = findByCsid(collectionObjectResource, csid);
268 if (Tools.notBlank(collectionObjectPayload.toXML())) {
269 if (logger.isInfoEnabled()) {
270 logger.info("Payload: " + "\n" + collectionObjectPayload);
272 // Silently fails at various places in dom4j calls (selectSingleNode, selectNode,
273 // createXpath) in any of the methods tried above, without throwing an Exception
275 objectNumber = getFieldValue(collectionObjectPayload,
276 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME,
277 "ns2", "http://collectionspace.org/services/collectionobject",
278 OBJECT_NUMBER_ELEMENT_NAME);
279 if (logger.isInfoEnabled()) {
280 logger.info("Object number: " + objectNumber);
283 objectNumber = "BAR"; // FIXME
284 if (Tools.notBlank(objectNumber)) {
285 String collectionObjectUpdatePayload =
286 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
287 + "<document name=\"collectionobject\">"
288 + "<ns2:collectionobjects_common xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
289 + "<objectNumber>" + objectNumber + "</objectNumber>"
290 + "<computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
291 + "</ns2:collectionobjects_common></document>";
292 if (logger.isInfoEnabled()) {
293 logger.info("Update payload: " + "\n" + collectionObjectUpdatePayload);
295 byte[] response = collectionObjectResource.update(resourcemap, null, csid, collectionObjectUpdatePayload);
297 if (logger.isTraceEnabled()) {
298 logger.trace("Computed current location value for CollectionObject " + csid + " set to " + computedCurrentLocation);
305 } catch (Exception e) {
306 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
307 errMsg = errMsg + "Successfully updated " + numAffected + " CollectionObject record(s) prior to error.";
308 setErrorResult(errMsg);
309 getResults().setNumAffected(numAffected);
313 getResults().setNumAffected(numAffected);