]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
eed4391350070c90054fefe93622e99bfc5ea440
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.batch.nuxeo;
2
3 import java.net.URI;
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;
9 import java.util.List;
10 import java.util.Map;
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;
28
29 import org.jdom.Document;
30 import org.jdom.Element;
31 import org.jdom.input.SAXBuilder;
32
33 import java.io.StringReader;
34
35 import org.dom4j.DocumentException;
36
37
38 /*
39  import org.dom4j.DocumentHelper;
40  import org.dom4j.Element;
41  import org.dom4j.Node;
42  import org.dom4j.XPath;
43  */
44
45 import org.jboss.resteasy.specimpl.UriInfoImpl;
46 import org.jdom.Namespace;
47 import org.nuxeo.ecm.core.api.DocumentModel;
48 import org.nuxeo.ecm.core.api.DocumentModelList;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
53
54     // FIXME: Where appropriate, get from existing constants rather than local declarations
55     private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
56     private final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common";
57     private final static String MOVEMENTS_COMMON_SCHEMA = MovementConstants.NUXEO_SCHEMA_NAME;
58     private final static String COMPUTED_CURRENT_LOCATION_ELEMENT_NAME = "computedCurrentLocation";
59     private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation";
60     private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
61     private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
62     private final static Namespace COLLECTIONOBJECTS_COMMON_NAMESPACE =
63             Namespace.getNamespace("ns2", "http://collectionspace.org/services/collectionobject");
64     private InvocationResults results = new InvocationResults();
65     private final String CLASSNAME = this.getClass().getSimpleName();
66     private final Logger logger = LoggerFactory.getLogger(UpdateObjectLocationBatchJob.class);
67
68     // Initialization tasks
69     public UpdateObjectLocationBatchJob() {
70         setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST));
71     }
72
73     /**
74      * The main work logic of the batch job. Will be called after setContext.
75      */
76     @Override
77     public void run() {
78
79         setCompletionStatus(STATUS_MIN_PROGRESS);
80
81         try {
82             // FIXME: Placeholder during early development
83             if (logger.isInfoEnabled()) {
84                 logger.info("Invoking " + CLASSNAME + " ...");
85                 logger.info("Invocation context is: " + getInvocationContext().getMode());
86             }
87
88             if (!requestedInvocationModeIsSupported()) {
89                 setInvocationModeNotSupportedResult();
90             }
91
92             List<String> csids = new ArrayList<String>();
93             if (requestIsForInvocationModeSingle()) {
94                 String singleCsid = getInvocationContext().getSingleCSID();
95                 if (Tools.notBlank(singleCsid)) {
96                     csids.add(singleCsid);
97                 }
98             } else if (requestIsForInvocationModeList()) {
99                 List<String> listCsids = (getInvocationContext().getListCSIDs().getCsid());
100                 if (listCsids == null || listCsids.isEmpty()) {
101                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
102                 }
103                 csids.addAll(listCsids);
104             } else if (requestIsForInvocationModeGroup()) {
105                 String groupCsid = getInvocationContext().getGroupCSID();
106                 // FIXME: Get individual CSIDs from the group
107                 // and add them to the list
108             }
109
110             if (csids.isEmpty()) {
111                 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT_MESSAGE);
112             }
113
114             // Update the current computed location field for each CollectionObject
115             setResults(updateComputedCurrentLocations(csids));
116             setCompletionStatus(STATUS_COMPLETE);
117
118         } catch (Exception e) {
119             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
120             setErrorResult(errMsg);
121         }
122
123     }
124
125     // Ray's convenience methods from his AbstractBatchJob class for the UC Berkeley Botanical Garden v2.4 implementation.
126     protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
127         ResourceBase resource = getResourceMap().get(serviceName);
128         return findByCsid(resource, csid);
129     }
130
131     protected PoxPayloadOut findByCsid(ResourceBase resource, String csid) throws URISyntaxException, DocumentException {
132         byte[] response = resource.get(null, createUriInfo(), csid);
133         PoxPayloadOut payload = new PoxPayloadOut(response);
134         return payload;
135     }
136
137     protected UriInfo createUriInfo() throws URISyntaxException {
138         return createUriInfo("");
139     }
140
141     protected UriInfo createUriInfo(String queryString) throws URISyntaxException {
142         URI absolutePath = new URI("");
143         URI baseUri = new URI("");
144         return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
145     }
146
147     protected UriInfo createRelationSearchUriInfo(String subjectCsid, String objType) throws URISyntaxException {
148         String queryString = "sbj=" + subjectCsid + "&objType=" + objType;
149         URI uri = new URI(null, null, null, queryString, null);
150         return createUriInfo(uri.getRawQuery());
151     }
152
153     protected String getFieldValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
154         String value = null;
155         SAXBuilder builder = new SAXBuilder();
156         try {
157             Document document = builder.build(new StringReader(payload.toXML()));
158             Element root = document.getRootElement();
159             // The part element is always expected to have an explicit namespace.
160             Element part = root.getChild(partLabel, partNamespace);
161             // Try getting the field element both with and without a namespace.
162             // Even though a field element that lacks a namespace prefix
163             // may yet inherit its namespace from a parent, JDOM may require that
164             // the getChild() call be made without a namespace.
165             Element field = part.getChild(fieldPath, partNamespace);
166             if (field == null) {
167                 field = part.getChild(fieldPath);
168             }
169             if (field != null) {
170                 value = field.getText();
171             }
172         } catch (Exception e) {
173             logger.error("Error getting value from field path " + fieldPath
174                     + " in schema part " + partLabel);
175             return null;
176         }
177         return value;
178     }
179
180     /**
181      * Get a field value from a PoxPayloadOut, given a part name and xpath
182      * expression.
183      */
184     /*
185      protected String getFieldValue(PoxPayloadOut payload, String partLabel, String fieldPath) {
186      String value = null;
187      PayloadOutputPart part = payload.getPart(partLabel);
188
189      if (part != null) {
190      Element element = part.asElement();
191      Node node = element.selectSingleNode(fieldPath);
192
193      if (node != null) {
194      value = node.getText();
195      }
196      }
197
198      return value;
199      }
200      */
201     /**
202      * Get a field value from a PoxPayloadOut, given a part name and
203      * namespace-qualified xpath expression.
204      */
205     /*
206      protected String getFieldValue(PoxPayloadOut payload, String partLabel, String namespacePrefix, String namespace, String fieldPath) {
207      String value = null;
208      PayloadOutputPart part = payload.getPart(partLabel);
209
210      if (part != null) {
211      Element element = part.asElement();
212      logger.info(partLabel + " part element =" + element.asXML());
213
214      Map<String, String> namespaceUris = new HashMap<String, String>();
215      namespaceUris.put(namespacePrefix, namespace);
216
217      XPath xPath = DocumentHelper.createXPath(fieldPath);
218      xPath.setNamespaceURIs(namespaceUris);
219
220      Node node = xPath.selectSingleNode(element);
221      // Node node = element.selectSingleNode(fieldPath);
222
223      if (node != null) {
224      value = node.getText();
225      }
226      }
227
228      return value;
229      }
230      */
231     private InvocationResults updateComputedCurrentLocations(List<String> csids) {
232
233         ResourceMap resourcemap = getResourceMap();
234         ResourceBase collectionObjectResource = resourcemap.get(CollectionObjectClient.SERVICE_NAME);
235         ResourceBase movementResource = resourcemap.get(MovementClient.SERVICE_NAME);
236         PoxPayloadOut collectionObjectPayload;
237         String objectNumber;
238         String computedCurrentLocation;
239         int numAffected = 0;
240
241         try {
242
243             // For each CollectionObject record:
244             for (String csid : csids) {
245
246                 // Get the movement records related to this record
247
248                 // FIXME: Create a convenience method for constructing queries like the following
249                 String queryString = "rtObj=" + csid; // FIXME: Get from constant
250                 URI uri = new URI(null, null, null, queryString, null);
251                 UriInfo uriInfo = createUriInfo(uri.getRawQuery());
252
253                 AbstractCommonList relatedMovements = movementResource.getList(uriInfo);
254                 if (logger.isInfoEnabled()) {
255                     logger.info("Identified " + relatedMovements.getTotalItems()
256                             + " Movement records related to the object CollectionObject record " + csid);
257                 }
258
259                 // Get relation records in the reverse direction as well,
260                 // merge with records obtained above, and remove duplicates
261                 queryString = "rtSbj=" + csid; // FIXME: Get from constant
262                 uri = new URI(null, null, null, queryString, null);
263                 uriInfo = createUriInfo(uri.getRawQuery());
264
265                 AbstractCommonList reverseRelatedMovements = movementResource.getList(uriInfo);
266                 if (logger.isInfoEnabled()) {
267                     logger.info("Identified " + reverseRelatedMovements.getTotalItems()
268                             + " Movement records related to the subject CollectionObject record " + csid);
269                 }
270
271                 if ((relatedMovements.getTotalItems() == 0) && reverseRelatedMovements.getTotalItems() == 0) {
272                     continue;
273                 }
274
275                 // Merge the two lists
276                 relatedMovements.getListItem().addAll(reverseRelatedMovements.getListItem());
277
278                 // Get the latest movement record from among those, and extract
279                 // its current location value
280                 computedCurrentLocation = "";
281                 String currentLocation;
282                 String locationDate;
283                 String mostRecentLocationDate = "";
284                 for (AbstractCommonList.ListItem movementRecord : relatedMovements.getListItem()) {
285
286                     // FIXME: Add 'de-duping' code here to avoid processing any
287                     // related Movement record more than once
288
289                     locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, LOCATION_DATE_ELEMENT_NAME);
290                     if (Tools.isBlank(locationDate)) {
291                         continue;
292                     }
293                     currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementRecord, CURRENT_LOCATION_ELEMENT_NAME);
294                     if (Tools.isBlank(currentLocation)) {
295                         continue;
296                     }
297                     if (logger.isInfoEnabled()) {
298                         logger.info("Location date value = " + locationDate);
299                         logger.info("Current location value = " + currentLocation);
300                     }
301                     // Assumes that all values for this element/field will be consistent ISO 8601
302                     // date/time representations, each of which can be ordered via string comparison.
303                     if (locationDate.compareTo(mostRecentLocationDate) > 0) {
304                         mostRecentLocationDate = locationDate;
305                         // FIXME: Add validation here that the currentLocation value parses successfully as an item refName
306                         computedCurrentLocation = currentLocation;
307                     }
308
309                 }
310
311                 // Update the computed current location value in the CollectionObject record
312                 collectionObjectPayload = findByCsid(collectionObjectResource, csid);
313                 if (Tools.notBlank(collectionObjectPayload.toXML())) {
314                     if (logger.isInfoEnabled()) {
315                         logger.info("Payload: " + "\n" + collectionObjectPayload);
316                     }
317                     // Silently fails at various places in dom4j calls (selectSingleNode, selectNode,
318                     // createXpath) in any of the methods tried, without throwing an Exception.
319                     //
320                     // Those methods are now commented out, in favor of a replacement, however temporary,
321                     // using JDOM.
322                     //
323                     // FIXME: Get namespace from constant; verify whether prefix or URI is required
324                     objectNumber = getFieldValue(collectionObjectPayload,
325                             COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
326                             OBJECT_NUMBER_ELEMENT_NAME);
327                     if (logger.isInfoEnabled()) {
328                         logger.info("Object number: " + objectNumber);
329                     }
330                     if (Tools.notBlank(objectNumber)) {
331                         String collectionObjectUpdatePayload =
332                                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
333                                 + "<document name=\"collectionobject\">"
334                                 + "<ns2:collectionobjects_common xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
335                                 + "<objectNumber>" + objectNumber + "</objectNumber>"
336                                 + "<computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
337                                 + "</ns2:collectionobjects_common></document>";
338                         if (logger.isInfoEnabled()) {
339                             logger.info("Update payload: " + "\n" + collectionObjectUpdatePayload);
340                         }
341                         byte[] response = collectionObjectResource.update(resourcemap, null, csid, collectionObjectUpdatePayload);
342                         numAffected++;
343                         if (logger.isTraceEnabled()) {
344                             logger.trace("Computed current location value for CollectionObject " + csid + " set to " + computedCurrentLocation);
345
346                         }
347                     }
348
349                 }
350             }
351         } catch (Exception e) {
352             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
353             errMsg = errMsg + "Successfully updated " + numAffected + " CollectionObject record(s) prior to error.";
354             logger.error(errMsg);
355             setErrorResult(errMsg);
356             getResults().setNumAffected(numAffected);
357             return getResults();
358         }
359
360         getResults().setNumAffected(numAffected);
361         return getResults();
362     }
363 }