]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
60be464f312e8f63a7b07dbd1ec2d3814bf53882
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.batch.nuxeo;
2
3 import java.io.StringReader;
4 import java.net.URI;
5 import java.net.URISyntaxException;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collections;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Set;
12 import javax.ws.rs.core.PathSegment;
13 import javax.ws.rs.core.Response;
14 import javax.ws.rs.core.UriInfo;
15 import org.collectionspace.services.batch.AbstractBatchInvocable;
16 import org.collectionspace.services.client.AbstractCommonListUtils;
17 import org.collectionspace.services.client.CollectionObjectClient;
18 import org.collectionspace.services.client.MovementClient;
19 import org.collectionspace.services.client.PoxPayloadOut;
20 import org.collectionspace.services.client.workflow.WorkflowClient;
21 import org.collectionspace.services.common.ResourceBase;
22 import org.collectionspace.services.common.ResourceMap;
23 import org.collectionspace.services.common.api.Tools;
24 import org.collectionspace.services.common.invocable.InvocationResults;
25 import org.collectionspace.services.jaxb.AbstractCommonList;
26 import org.dom4j.DocumentException;
27 import org.jboss.resteasy.specimpl.UriInfoImpl;
28 import org.jdom.Document;
29 import org.jdom.Element;
30 import org.jdom.Namespace;
31 import org.jdom.input.SAXBuilder;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
36
37     // FIXME: Where appropriate, get from existing constants rather than local declarations
38     private final static String CSID_ELEMENT_NAME = "csid";
39     private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation";
40     private final static String LIFECYCLE_STATE_ELEMENT_NAME = "currentLifeCycleState";
41     private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
42     private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
43     private final static String WORKFLOW_COMMON_SCHEMA_NAME = "workflow_common";
44     private final static String WORKFLOW_COMMON_NAMESPACE_PREFIX = "ns2";
45     private final static String WORKFLOW_COMMON_NAMESPACE_URI =
46             "http://collectionspace.org/services/workflow";
47     private final static Namespace WORKFLOW_COMMON_NAMESPACE =
48             Namespace.getNamespace(
49             WORKFLOW_COMMON_NAMESPACE_PREFIX,
50             WORKFLOW_COMMON_NAMESPACE_URI);
51     private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
52     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX = "ns2";
53     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_URI =
54             "http://collectionspace.org/services/collectionobject";
55     private final static Namespace COLLECTIONOBJECTS_COMMON_NAMESPACE =
56             Namespace.getNamespace(
57             COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX,
58             COLLECTIONOBJECTS_COMMON_NAMESPACE_URI);
59     private final static String NONDELETED_QUERY_COMPONENT =
60             "&" + WorkflowClient.WORKFLOW_QUERY_NONDELETED + "=false";
61     private PoxPayloadOut payloadOut;
62     private final String CLASSNAME = this.getClass().getSimpleName();
63     private final Logger logger = LoggerFactory.getLogger(this.getClass());
64
65     // Initialization tasks
66     public UpdateObjectLocationBatchJob() {
67         setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST));
68     }
69
70     /**
71      * The main work logic of the batch job. Will be called after setContext.
72      */
73     @Override
74     public void run() {
75
76         setCompletionStatus(STATUS_MIN_PROGRESS);
77
78         try {
79
80             List<String> csids = new ArrayList<String>();
81             if (requestIsForInvocationModeSingle()) {
82                 String singleCsid = getInvocationContext().getSingleCSID();
83                 if (Tools.isBlank(singleCsid)) {
84                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
85                 } else {
86                     csids.add(singleCsid);
87                 }
88             } else if (requestIsForInvocationModeList()) {
89                 List<String> listCsids = getListCsids();
90                 if (listCsids.isEmpty()) {
91                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
92                 }
93                 csids.addAll(listCsids);
94             } else if (requestIsForInvocationModeGroup()) {
95                 // Currently not supported
96                 // FIXME: Get individual CSIDs, if any, from the group
97                 // String groupCsid = getInvocationContext().getGroupCSID();
98                 // ...
99             } else if (requestIsForInvocationModeNoContext()) {
100                 // Currently not supported
101                 // FIXME: Add code to invoke batch job on every active CollectionObject
102             }
103
104             // Update the computed current location field for each CollectionObject
105             setResults(updateComputedCurrentLocations(csids));
106             setCompletionStatus(STATUS_COMPLETE);
107
108         } catch (Exception e) {
109             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
110             setErrorResult(errMsg);
111         }
112
113     }
114
115     // #################################################################
116     // Ray's convenience methods from his AbstractBatchJob class for the
117     // UC Berkeley Botanical Garden v2.4 implementation.
118     // #################################################################
119     protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
120         ResourceBase resource = getResourceMap().get(serviceName);
121         return findByCsid(resource, csid);
122     }
123
124     protected PoxPayloadOut findByCsid(ResourceBase resource, String csid) throws URISyntaxException, DocumentException {
125         byte[] response = resource.get(null, createUriInfo(), csid);
126         PoxPayloadOut payload = new PoxPayloadOut(response);
127         return payload;
128     }
129
130     protected UriInfo createUriInfo() throws URISyntaxException {
131         return createUriInfo("");
132     }
133
134     protected UriInfo createUriInfo(String queryString) throws URISyntaxException {
135         URI absolutePath = new URI("");
136         URI baseUri = new URI("");
137         return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
138     }
139
140     // #################################################################
141     // Other convenience methods
142     // #################################################################
143     protected UriInfo createRelatedRecordsUriInfo(String query) throws URISyntaxException {
144         URI uri = new URI(null, null, null, query, null);
145         return createUriInfo(uri.getRawQuery());
146     }
147
148     protected String getFieldElementValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
149         String value = null;
150         SAXBuilder builder = new SAXBuilder();
151         try {
152             Document document = builder.build(new StringReader(payload.toXML()));
153             Element root = document.getRootElement();
154             // The part element is always expected to have an explicit namespace.
155             Element part = root.getChild(partLabel, partNamespace);
156             // Try getting the field element both with and without a namespace.
157             // Even though a field element that lacks a namespace prefix
158             // may yet inherit its namespace from a parent, JDOM may require that
159             // the getChild() call be made without a namespace.
160             Element field = part.getChild(fieldPath, partNamespace);
161             if (field == null) {
162                 field = part.getChild(fieldPath);
163             }
164             if (field != null) {
165                 value = field.getText();
166             }
167         } catch (Exception e) {
168             logger.error("Error getting value from field path " + fieldPath
169                     + " in schema part " + partLabel);
170             return null;
171         }
172         return value;
173     }
174
175     private boolean isRecordDeleted(ResourceBase resource, String collectionObjectCsid) throws URISyntaxException, DocumentException {
176         boolean isDeleted = false;
177         byte[] workflowResponse = resource.getWorkflow(createUriInfo(), collectionObjectCsid);
178         if (workflowResponse != null) {
179             payloadOut = new PoxPayloadOut(workflowResponse);
180             String workflowState =
181                     getFieldElementValue(payloadOut, WORKFLOW_COMMON_SCHEMA_NAME,
182                     WORKFLOW_COMMON_NAMESPACE, LIFECYCLE_STATE_ELEMENT_NAME);
183             if (Tools.notBlank(workflowState) && workflowState.contentEquals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
184                 isDeleted = true;
185             }
186         }
187         return isDeleted;
188     }
189
190     private InvocationResults updateComputedCurrentLocations(List<String> csids) {
191
192         ResourceMap resourcemap = getResourceMap();
193         ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
194         ResourceBase movementResource = resourcemap.get(MovementClient.SERVICE_NAME);
195         PoxPayloadOut collectionObjectPayload;
196         String computedCurrentLocation;
197         String objectNumber;
198         String movementCsid;
199         PoxPayloadOut payloadOut;
200         String queryString;
201         int numAffected = 0;
202
203         try {
204
205             // For each CollectionObject record
206             for (String collectionObjectCsid : csids) {
207
208                 // Skip over soft-deleted CollectionObject records
209                 if (isRecordDeleted(collectionObjectResource, collectionObjectCsid)) {
210                     if (logger.isTraceEnabled()) {
211                         logger.trace("Skipping soft-deleted CollectionObject record with CSID " + collectionObjectCsid);
212                     }
213                     continue;
214                 }
215
216                 // Get the movement records related to this record
217
218                 // Get movement records related to this record where the CollectionObject
219                 // record is the subject of the relation
220                 // FIXME: Get query string(s) from constant(s), where appropriate
221                 queryString = "rtObj=" + collectionObjectCsid + NONDELETED_QUERY_COMPONENT;
222                 UriInfo uriInfo = createRelatedRecordsUriInfo(queryString);
223
224                 AbstractCommonList relatedMovements = movementResource.getList(uriInfo);
225                 if (logger.isTraceEnabled()) {
226                     logger.trace("Identified " + relatedMovements.getTotalItems()
227                             + " Movement record(s) related to the object CollectionObject record " + collectionObjectCsid);
228                 }
229
230                 // Get movement records related to this record where the CollectionObject
231                 // record is the object of the relation
232                 // FIXME: Get query string(s) from constant(s), where appropriate
233                 queryString = "rtSbj=" + collectionObjectCsid + NONDELETED_QUERY_COMPONENT;
234                 uriInfo = createRelatedRecordsUriInfo(queryString);
235
236                 AbstractCommonList reverseRelatedMovements = movementResource.getList(uriInfo);
237                 if (logger.isTraceEnabled()) {
238                     logger.trace("Identified " + reverseRelatedMovements.getTotalItems()
239                             + " Movement record(s) related to the subject CollectionObject record " + collectionObjectCsid);
240                 }
241
242                 if ((relatedMovements.getTotalItems() == 0) && reverseRelatedMovements.getTotalItems() == 0) {
243                     continue;
244                 }
245
246                 // Merge the two lists of related movement records
247                 relatedMovements.getListItem().addAll(reverseRelatedMovements.getListItem());
248                 
249                 if (logger.isTraceEnabled()) {
250                     logger.trace("Identified a total of " + relatedMovements.getListItem().size()
251                             + " Movement record(s) related to the subject CollectionObject record " + collectionObjectCsid);
252                 }
253
254                 // Get the latest movement record from among those, and extract
255                 // its current location value
256                 Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
257                 computedCurrentLocation = "";
258                 String currentLocation;
259                 String locationDate;
260                 String mostRecentLocationDate = "";
261                 for (AbstractCommonList.ListItem movementRecord : relatedMovements.getListItem()) {
262                     movementCsid = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, CSID_ELEMENT_NAME);
263                     if (Tools.isBlank(movementCsid)) {
264                         continue;
265                     }
266                     // Avoid processing any related Movement record more than once,
267                     // regardless of the directionality of its relation(s) to this
268                     // CollectionObject record.
269                     if (alreadyProcessedMovementCsids.contains(movementCsid)) {
270                         continue;
271                     } else {
272                         alreadyProcessedMovementCsids.add(movementCsid);
273                     }
274                     locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, LOCATION_DATE_ELEMENT_NAME);
275                     if (Tools.isBlank(locationDate)) {
276                         continue;
277                     }
278                     currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, CURRENT_LOCATION_ELEMENT_NAME);
279                     if (Tools.isBlank(currentLocation)) {
280                         continue;
281                     }
282                     if (logger.isTraceEnabled()) {
283                         logger.trace("Location date value = " + locationDate);
284                         logger.trace("Current location value = " + currentLocation);
285                     }
286                     // Assumes that all values for this element/field will be consistent ISO 8601
287                     // date/time representations, each of which can be ordered via string comparison.
288                     //
289                     // If this is *not* the case, we can instead parse and convert these values
290                     // to date/time objects.
291                     if (locationDate.compareTo(mostRecentLocationDate) > 0) {
292                         mostRecentLocationDate = locationDate;
293                         // FIXME: Add optional validation here that the currentLocation value
294                         // parses successfully as an item refName
295                         computedCurrentLocation = currentLocation;
296                     }
297
298                 }
299
300                 // Update the computed current location value in the CollectionObject record
301                 collectionObjectPayload = findByCsid(collectionObjectResource, collectionObjectCsid);
302                 if (Tools.notBlank(collectionObjectPayload.toXML())) {
303                     if (logger.isTraceEnabled()) {
304                         logger.trace("Payload: " + "\n" + collectionObjectPayload);
305                     }
306                     objectNumber = getFieldElementValue(collectionObjectPayload,
307                             COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
308                             OBJECT_NUMBER_ELEMENT_NAME);
309                     if (logger.isTraceEnabled()) {
310                         logger.trace("Object number: " + objectNumber);
311                     }
312                     if (Tools.notBlank(objectNumber)) {
313                         String collectionObjectUpdatePayload =
314                                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
315                                 + "<document name=\"collectionobject\">"
316                                 + "  <ns2:collectionobjects_common "
317                                 + "      xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
318                                 + "    <objectNumber>" + objectNumber + "</objectNumber>"
319                                 + "    <computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
320                                 + "  </ns2:collectionobjects_common>"
321                                 + "</document>";
322                         if (logger.isTraceEnabled()) {
323                             logger.trace("Update payload: " + "\n" + collectionObjectUpdatePayload);
324                         }
325                         byte[] response = collectionObjectResource.update(resourcemap, null, collectionObjectCsid,
326                                 collectionObjectUpdatePayload);
327                         numAffected++;
328                         if (logger.isTraceEnabled()) {
329                             logger.trace("Computed current location value for CollectionObject " + collectionObjectCsid
330                                     + " was set to " + computedCurrentLocation);
331                         }
332                     }
333
334                 }
335             }
336         } catch (Exception e) {
337             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
338             errMsg = errMsg + "Successfully updated " + numAffected + " CollectionObject record(s) prior to error.";
339             logger.error(errMsg);
340             setErrorResult(errMsg);
341             getResults().setNumAffected(numAffected);
342             return getResults();
343         }
344
345         logger.info("Updated computedCurrentLocation values in " + numAffected + " CollectionObject record(s).");
346         getResults().setNumAffected(numAffected);
347         return getResults();
348     }
349 }