]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
7e01e9296746eb2eac86e8e46c9100f8b1450d87
[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.UriInfo;
14 import org.collectionspace.services.batch.AbstractBatchInvocable;
15 import org.collectionspace.services.client.AbstractCommonListUtils;
16 import org.collectionspace.services.client.CollectionObjectClient;
17 import org.collectionspace.services.client.MovementClient;
18 import org.collectionspace.services.client.PoxPayloadOut;
19 import org.collectionspace.services.client.workflow.WorkflowClient;
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.dom4j.DocumentException;
26 import org.jboss.resteasy.specimpl.UriInfoImpl;
27 import org.jdom.Document;
28 import org.jdom.Element;
29 import org.jdom.Namespace;
30 import org.jdom.input.SAXBuilder;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
35
36     // FIXME: Where appropriate, get from existing constants rather than local declarations
37     private final static String CSID_ELEMENT_NAME = "csid";
38     private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation";
39     private final static String LIFECYCLE_STATE_ELEMENT_NAME = "currentLifeCycleState";
40     private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
41     private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
42     private final static String WORKFLOW_COMMON_SCHEMA_NAME = "workflow_common";
43     private final static String WORKFLOW_COMMON_NAMESPACE_PREFIX = "ns2";
44     private final static String WORKFLOW_COMMON_NAMESPACE_URI =
45             "http://collectionspace.org/services/workflow";
46     private final static Namespace WORKFLOW_COMMON_NAMESPACE =
47             Namespace.getNamespace(
48             WORKFLOW_COMMON_NAMESPACE_PREFIX,
49             WORKFLOW_COMMON_NAMESPACE_URI);
50     private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
51     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX = "ns2";
52     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_URI =
53             "http://collectionspace.org/services/collectionobject";
54     private final static Namespace COLLECTIONOBJECTS_COMMON_NAMESPACE =
55             Namespace.getNamespace(
56             COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX,
57             COLLECTIONOBJECTS_COMMON_NAMESPACE_URI);
58     private final String CLASSNAME = this.getClass().getSimpleName();
59     private final Logger logger = LoggerFactory.getLogger(this.getClass());
60
61     // Initialization tasks
62     public UpdateObjectLocationBatchJob() {
63         setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST));
64     }
65
66     /**
67      * The main work logic of the batch job. Will be called after setContext.
68      */
69     @Override
70     public void run() {
71
72         setCompletionStatus(STATUS_MIN_PROGRESS);
73
74         try {
75
76             List<String> csids = new ArrayList<String>();
77
78             // Build a list of CollectionObject records to process via this
79             // batch job, depending on the invocation mode requested.
80             if (requestIsForInvocationModeSingle()) {
81                 String singleCsid = getInvocationContext().getSingleCSID();
82                 if (Tools.isBlank(singleCsid)) {
83                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
84                 } else {
85                     csids.add(singleCsid);
86                 }
87             } else if (requestIsForInvocationModeList()) {
88                 List<String> listCsids = getListCsids();
89                 if (listCsids.isEmpty()) {
90                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
91                 }
92                 csids.addAll(listCsids);
93             } else if (requestIsForInvocationModeGroup()) {
94                 // Currently not supported
95                 String groupCsid = getInvocationContext().getGroupCSID();
96                 List<String> groupMemberCsids = getMemberCsidsFromGroup(groupCsid);
97                 if (groupMemberCsids.isEmpty()) {
98                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
99                 }
100                 csids.addAll(groupMemberCsids);
101             } else if (requestIsForInvocationModeNoContext()) {
102                 List<String> noContextCsids = getNoContextCsids();
103                 if (noContextCsids.isEmpty()) {
104                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
105                 }
106                 csids.addAll(noContextCsids);
107             }
108
109             // Update the value of the computed current location field for each CollectionObject
110             setResults(updateComputedCurrentLocations(csids));
111             setCompletionStatus(STATUS_COMPLETE);
112
113         } catch (Exception e) {
114             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
115             setErrorResult(errMsg);
116         }
117
118     }
119
120     private InvocationResults updateComputedCurrentLocations(List<String> csids) {
121
122         ResourceMap resourcemap = getResourceMap();
123         ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
124         ResourceBase movementResource = resourcemap.get(MovementClient.SERVICE_NAME);
125         String computedCurrentLocation;
126         int numUpdated = 0;
127
128         try {
129
130             // For each CollectionObject record
131             for (String collectionObjectCsid : csids) {
132
133                 // Skip over soft-deleted CollectionObject records
134                 if (isRecordDeleted(collectionObjectResource, collectionObjectCsid)) {
135                     if (logger.isTraceEnabled()) {
136                         logger.trace("Skipping soft-deleted CollectionObject record with CSID " + collectionObjectCsid);
137                     }
138                     continue;
139                 }
140                 // Get the Movement records related to this CollectionObject record
141                 AbstractCommonList relatedMovements =
142                         getRelatedRecords(movementResource, collectionObjectCsid, true /* exclude deleted records */);
143                 // Skip over CollectionObject records that have no related Movement records
144                 if (relatedMovements.getListItem().isEmpty()) {
145                     continue;
146                 }
147                 // Compute the current location of this CollectionObject,
148                 // based on data in its related Movement records
149                 computedCurrentLocation = computeCurrentLocation(relatedMovements);
150                 // Skip over CollectionObject records where no current location
151                 // value can be computed from related Movement records
152                 //
153                 // FIXME: Clarify: it ever necessary to 'unset' a computed
154                 // current location value, by setting it to a null or empty value,
155                 // if that value is no longer obtainable from related Movement records?
156                 if (Tools.isBlank(computedCurrentLocation)) {
157                     continue;
158                 }
159                 // Update the value of the computed current location field
160                 // in the CollectionObject record
161                 numUpdated = updateComputedCurrentLocationValue(collectionObjectResource,
162                         collectionObjectCsid, computedCurrentLocation, resourcemap, numUpdated);
163             }
164
165         } catch (Exception e) {
166             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
167             errMsg = errMsg + "Successfully updated " + numUpdated + " CollectionObject record(s) prior to error.";
168             logger.error(errMsg);
169             setErrorResult(errMsg);
170             getResults().setNumAffected(numUpdated);
171             return getResults();
172         }
173
174         logger.info("Updated computedCurrentLocation values in " + numUpdated + " CollectionObject record(s).");
175         getResults().setNumAffected(numUpdated);
176         return getResults();
177     }
178
179     private String computeCurrentLocation(AbstractCommonList relatedMovements) {
180         String computedCurrentLocation;
181         String movementCsid;
182         Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
183         computedCurrentLocation = "";
184         String currentLocation;
185         String locationDate;
186         String mostRecentLocationDate = "";
187         for (AbstractCommonList.ListItem movementRecord : relatedMovements.getListItem()) {
188             movementCsid = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, CSID_ELEMENT_NAME);
189             if (Tools.isBlank(movementCsid)) {
190                 continue;
191             }
192             // Avoid processing any related Movement record more than once,
193             // regardless of the directionality of its relation(s) to this
194             // CollectionObject record.
195             if (alreadyProcessedMovementCsids.contains(movementCsid)) {
196                 continue;
197             } else {
198                 alreadyProcessedMovementCsids.add(movementCsid);
199             }
200             locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, LOCATION_DATE_ELEMENT_NAME);
201             if (Tools.isBlank(locationDate)) {
202                 continue;
203             }
204             currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, CURRENT_LOCATION_ELEMENT_NAME);
205             if (Tools.isBlank(currentLocation)) {
206                 continue;
207             }
208             if (logger.isTraceEnabled()) {
209                 logger.trace("Location date value = " + locationDate);
210                 logger.trace("Current location value = " + currentLocation);
211             }
212             // If this record's location date value is more recent than that of other
213             // Movement records processed so far, set the computed current location
214             // to its current location value.
215             //
216             // Assumes that all values for this element/field will be consistent ISO 8601
217             // date/time representations, each of which can be ordered via string comparison.
218             //
219             // If this is *not* the case, we can instead parse and convert these values
220             // to date/time objects.
221             if (locationDate.compareTo(mostRecentLocationDate) > 0) {
222                 mostRecentLocationDate = locationDate;
223                 // FIXME: Add optional validation here that the currentLocation value
224                 // parses successfully as an item refName. (We might make that validation
225                 // dependent on the value of a parameter passed in during batch job invocation.)
226                 computedCurrentLocation = currentLocation;
227             }
228
229         }
230         return computedCurrentLocation;
231     }
232
233     private int updateComputedCurrentLocationValue(ResourceBase collectionObjectResource,
234             String collectionObjectCsid, String computedCurrentLocation, ResourceMap resourcemap, int numUpdated)
235             throws DocumentException, URISyntaxException {
236         PoxPayloadOut collectionObjectPayload;
237         String objectNumber;
238
239         collectionObjectPayload = findByCsid(collectionObjectResource, collectionObjectCsid);
240         if (Tools.notBlank(collectionObjectPayload.toXML())) {
241             if (logger.isTraceEnabled()) {
242                 logger.trace("Payload: " + "\n" + collectionObjectPayload);
243             }
244             objectNumber = getFieldElementValue(collectionObjectPayload,
245                     COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
246                     OBJECT_NUMBER_ELEMENT_NAME);
247             if (logger.isTraceEnabled()) {
248                 logger.trace("Object number: " + objectNumber);
249             }
250             if (Tools.notBlank(objectNumber)) {
251                 String collectionObjectUpdatePayload =
252                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
253                         + "<document name=\"collectionobject\">"
254                         + "  <ns2:collectionobjects_common "
255                         + "      xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
256                         + "    <objectNumber>" + objectNumber + "</objectNumber>"
257                         + "    <computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
258                         + "  </ns2:collectionobjects_common>"
259                         + "</document>";
260                 if (logger.isTraceEnabled()) {
261                     logger.trace("Update payload: " + "\n" + collectionObjectUpdatePayload);
262                 }
263                 byte[] response = collectionObjectResource.update(resourcemap, null, collectionObjectCsid,
264                         collectionObjectUpdatePayload);
265                 numUpdated++;
266                 if (logger.isTraceEnabled()) {
267                     logger.trace("Computed current location value for CollectionObject " + collectionObjectCsid
268                             + " was set to " + computedCurrentLocation);
269                 }
270             }
271
272         }
273         return numUpdated;
274     }
275
276     // #################################################################
277     // Ray's convenience methods from his AbstractBatchJob class for the
278     // UC Berkeley Botanical Garden v2.4 implementation.
279     // #################################################################
280     protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
281         ResourceBase resource = getResourceMap().get(serviceName);
282         return findByCsid(resource, csid);
283     }
284
285     protected PoxPayloadOut findByCsid(ResourceBase resource, String csid) throws URISyntaxException, DocumentException {
286         byte[] response = resource.get(null, createUriInfo(), csid);
287         PoxPayloadOut payload = new PoxPayloadOut(response);
288         return payload;
289     }
290
291     protected UriInfo createUriInfo() throws URISyntaxException {
292         return createUriInfo("");
293     }
294
295     protected UriInfo createUriInfo(String queryString) throws URISyntaxException {
296         URI absolutePath = new URI("");
297         URI baseUri = new URI("");
298         return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
299     }
300
301     // #################################################################
302     // Other convenience methods
303     // #################################################################
304     protected UriInfo createRelatedRecordsUriInfo(String queryString) throws URISyntaxException {
305         URI uri = new URI(null, null, null, queryString, null);
306         return createUriInfo(uri.getRawQuery());
307     }
308
309     protected String getFieldElementValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
310         String value = null;
311         SAXBuilder builder = new SAXBuilder();
312         try {
313             Document document = builder.build(new StringReader(payload.toXML()));
314             Element root = document.getRootElement();
315             // The part element is always expected to have an explicit namespace.
316             Element part = root.getChild(partLabel, partNamespace);
317             // Try getting the field element both with and without a namespace.
318             // Even though a field element that lacks a namespace prefix
319             // may yet inherit its namespace from a parent, JDOM may require that
320             // the getChild() call be made without a namespace.
321             Element field = part.getChild(fieldPath, partNamespace);
322             if (field == null) {
323                 field = part.getChild(fieldPath);
324             }
325             if (field != null) {
326                 value = field.getText();
327             }
328         } catch (Exception e) {
329             logger.error("Error getting value from field path " + fieldPath
330                     + " in schema part " + partLabel);
331             return null;
332         }
333         return value;
334     }
335
336     private boolean isRecordDeleted(ResourceBase resource, String collectionObjectCsid)
337             throws URISyntaxException, DocumentException {
338         boolean isDeleted = false;
339         byte[] workflowResponse = resource.getWorkflow(createUriInfo(), collectionObjectCsid);
340         if (workflowResponse != null) {
341             PoxPayloadOut payloadOut = new PoxPayloadOut(workflowResponse);
342             String workflowState =
343                     getFieldElementValue(payloadOut, WORKFLOW_COMMON_SCHEMA_NAME,
344                     WORKFLOW_COMMON_NAMESPACE, LIFECYCLE_STATE_ELEMENT_NAME);
345             if (Tools.notBlank(workflowState) && workflowState.equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
346                 isDeleted = true;
347             }
348         }
349         return isDeleted;
350     }
351
352     private AbstractCommonList getRelatedRecords(ResourceBase resource, String csid, boolean excludeDeletedRecords)
353             throws URISyntaxException, DocumentException {
354
355         // Get records related to a record, specified by its CSID,
356         // where the record is the object of the relation
357         UriInfo uriInfo = createUriInfo();
358         // FIXME: Get this from constant(s), where appropriate
359         uriInfo.getQueryParameters().add("rtObj", csid);
360         if (excludeDeletedRecords) {
361             uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_NONDELETED, "false");
362         }
363
364         AbstractCommonList relatedRecords = resource.getList(uriInfo);
365         if (logger.isTraceEnabled()) {
366             logger.trace("Identified " + relatedRecords.getTotalItems()
367                     + " record(s) related to the object record with CSID " + csid);
368         }
369
370         // Get records related to a record, specified by its CSID,
371         // where the record is the subject of the relation
372         // FIXME: Get query string(s) from constant(s), where appropriate
373         uriInfo = createUriInfo();
374         uriInfo.getQueryParameters().add("rtSbj", csid);
375         if (excludeDeletedRecords) {
376             uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_NONDELETED, "false");
377         }
378         AbstractCommonList reverseRelatedRecords = resource.getList(uriInfo);
379         if (logger.isTraceEnabled()) {
380             logger.trace("Identified " + reverseRelatedRecords.getTotalItems()
381                     + " record(s) related to the subject record with CSID " + csid);
382         }
383
384         // If the second list contains any related records,
385         // merge it into the first list
386         if (reverseRelatedRecords.getListItem().size() > 0) {
387             relatedRecords.getListItem().addAll(reverseRelatedRecords.getListItem());
388         }
389
390         if (logger.isTraceEnabled()) {
391             logger.trace("Identified a total of " + relatedRecords.getListItem().size()
392                     + " record(s) related to the record with CSID " + csid);
393         }
394
395         return relatedRecords;
396     }
397
398     // Not currently supported
399     private List<String> getMemberCsidsFromGroup(String groupCsid) throws URISyntaxException {
400         List<String> memberCsids = Collections.emptyList();
401         return memberCsids;
402     }
403
404     private List<String> getNoContextCsids() throws URISyntaxException {
405         List<String> noContextCsids = Collections.emptyList();
406         ResourceMap resourcemap = getResourceMap();
407         ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
408         UriInfo uriInfo = createUriInfo();
409         uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_NONDELETED, "false");
410         AbstractCommonList collectionObjects = collectionObjectResource.getList(uriInfo);
411         for (AbstractCommonList.ListItem collectionObjectRecord : collectionObjects.getListItem()) {
412             noContextCsids.add(AbstractCommonListUtils.ListItemGetElementValue(collectionObjectRecord, CSID_ELEMENT_NAME));
413         }
414         if (logger.isInfoEnabled()) {
415             logger.info("Identified " + noContextCsids.size()
416                     + " total active CollectionObjects to process in the 'no context' invocation mode.");
417         }
418         return noContextCsids;
419
420     }
421 }