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