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