]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
572df62c24403ded0a961a88b75ace95efe98fb5
[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
13 import javax.ws.rs.core.PathSegment;
14 import javax.ws.rs.core.UriInfo;
15
16 import org.collectionspace.services.batch.AbstractBatchInvocable;
17 import org.collectionspace.services.batch.UriInfoImpl;
18 import org.collectionspace.services.client.AbstractCommonListUtils;
19 import org.collectionspace.services.client.CollectionObjectClient;
20 import org.collectionspace.services.client.IQueryManager;
21 import org.collectionspace.services.client.MovementClient;
22 import org.collectionspace.services.client.PoxPayloadOut;
23 import org.collectionspace.services.client.workflow.WorkflowClient;
24 import org.collectionspace.services.common.NuxeoBasedResource;
25 import org.collectionspace.services.common.ResourceMap;
26 import org.collectionspace.services.common.api.RefNameUtils;
27 import org.collectionspace.services.common.api.Tools;
28 import org.collectionspace.services.common.invocable.InvocationResults;
29 import org.collectionspace.services.jaxb.AbstractCommonList;
30 import org.dom4j.DocumentException;
31 //import org.jboss.resteasy.specimpl.UriInfoImpl;
32 import org.jdom.Document;
33 import org.jdom.Element;
34 import org.jdom.Namespace;
35 import org.jdom.input.SAXBuilder;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
40
41     // FIXME: Where appropriate, get from existing constants rather than local declarations
42     private final static String COMPUTED_CURRENT_LOCATION_ELEMENT_NAME = "computedCurrentLocation";
43     private final static String CSID_ELEMENT_NAME = "csid";
44     private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation";
45     private final static String LIFECYCLE_STATE_ELEMENT_NAME = "currentLifeCycleState";
46     private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
47     private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
48     private final static String UPDATE_DATE_ELEMENT_NAME = "updatedAt";
49     private final static String WORKFLOW_COMMON_SCHEMA_NAME = "workflow_common";
50     private final static String WORKFLOW_COMMON_NAMESPACE_PREFIX = "ns2";
51     private final static String WORKFLOW_COMMON_NAMESPACE_URI =
52             "http://collectionspace.org/services/workflow";
53     private final static Namespace WORKFLOW_COMMON_NAMESPACE =
54             Namespace.getNamespace(
55             WORKFLOW_COMMON_NAMESPACE_PREFIX,
56             WORKFLOW_COMMON_NAMESPACE_URI);
57     private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
58     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX = "ns2";
59     private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_URI =
60             "http://collectionspace.org/services/collectionobject";
61     private final static Namespace COLLECTIONOBJECTS_COMMON_NAMESPACE =
62             Namespace.getNamespace(
63             COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX,
64             COLLECTIONOBJECTS_COMMON_NAMESPACE_URI);
65     private final boolean EXCLUDE_DELETED = true;
66     private final String CLASSNAME = this.getClass().getSimpleName();
67     private final Logger logger = LoggerFactory.getLogger(this.getClass());
68
69     // Initialization tasks
70     public UpdateObjectLocationBatchJob() {
71         setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST,
72                 INVOCATION_MODE_GROUP, INVOCATION_MODE_NO_CONTEXT));
73     }
74
75     /**
76      * The main work logic of the batch job. Will be called after setContext.
77      */
78     @Override
79     public void run() {
80
81         setCompletionStatus(STATUS_MIN_PROGRESS);
82
83         try {
84
85             List<String> csids = new ArrayList<String>();
86
87             // Build a list of CollectionObject records to process via this
88             // batch job, depending on the invocation mode requested.
89             if (requestIsForInvocationModeSingle()) {
90                 String singleCsid = getInvocationContext().getSingleCSID();
91                 if (Tools.isBlank(singleCsid)) {
92                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
93                 } else {
94                     csids.add(singleCsid);
95                 }
96             } else if (requestIsForInvocationModeList()) {
97                 List<String> listCsids = getListCsids();
98                 if (listCsids.isEmpty()) {
99                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
100                 }
101                 csids.addAll(listCsids);
102             } else if (requestIsForInvocationModeGroup()) {
103                 String groupCsid = getInvocationContext().getGroupCSID();
104                 if (Tools.isBlank(groupCsid)) {
105                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
106                 }
107                 List<String> groupMemberCsids = getMemberCsidsFromGroup(CollectionObjectClient.SERVICE_NAME, groupCsid);
108                 if (groupMemberCsids.isEmpty()) {
109                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
110                 }
111                 csids.addAll(groupMemberCsids);
112             } else if (requestIsForInvocationModeNoContext()) {
113                 List<String> noContextCsids = getNoContextCsids();
114                 if (noContextCsids.isEmpty()) {
115                     throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
116                 }
117                 csids.addAll(noContextCsids);
118             }
119             if (logger.isInfoEnabled()) {
120                 logger.info("Identified " + csids.size() + " total CollectionObject(s) to be processed via the " + CLASSNAME + " batch job");
121             }
122
123             // Update the value of the computed current location field for each CollectionObject
124             setResults(updateComputedCurrentLocations(csids));
125             setCompletionStatus(STATUS_COMPLETE);
126
127         } catch (Exception e) {
128             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
129             setErrorResult(errMsg);
130         }
131
132     }
133
134     private InvocationResults updateComputedCurrentLocations(List<String> csids) {
135         ResourceMap resourcemap = getResourceMap();
136         NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
137         NuxeoBasedResource movementResource = (NuxeoBasedResource) resourcemap.get(MovementClient.SERVICE_NAME);
138         String computedCurrentLocation;
139         int numUpdated = 0;
140
141         try {
142
143             // For each CollectionObject record
144             for (String collectionObjectCsid : csids) {
145
146                 // FIXME: Optionally set competition status here to
147                 // indicate what percentage of records have been processed.
148
149                 // Skip over soft-deleted CollectionObject records
150                 //
151                 // (Invocations using the 'no context' mode have already
152                 // filtered out soft-deleted records.)
153                 if (!requestIsForInvocationModeNoContext()) {
154                     if (isRecordDeleted(collectionObjectResource, collectionObjectCsid)) {
155                         if (logger.isTraceEnabled()) {
156                             logger.trace("Skipping soft-deleted CollectionObject record with CSID " + collectionObjectCsid);
157                         }
158                         continue;
159                     }
160                 }
161                 // Get the Movement records related to this CollectionObject record
162                 AbstractCommonList relatedMovements =
163                         getRelatedRecords(movementResource, collectionObjectCsid, EXCLUDE_DELETED);
164                 // Skip over CollectionObject records that have no related Movement records
165                 if (relatedMovements.getListItem().isEmpty()) {
166                     continue;
167                 }
168                 // Get the most recent 'suitable' Movement record, one which
169                 // contains both a location date and a current location value
170                 AbstractCommonList.ListItem mostRecentMovement = getMostRecentMovement(relatedMovements);
171                 // Skip over CollectionObject records where no suitable
172                 // most recent Movement record can be identified.
173                 //
174                 // FIXME: Clarify: it ever necessary to 'unset' a computed
175                 // current location value, by setting it to a null or empty value,
176                 // if that value is no longer obtainable from related Movement
177                 // records, if any?
178                 if (mostRecentMovement == null) {
179                     continue;
180                 }
181                 // Update the value of the computed current location field
182                 // (and, via subclasses, this and/or other relevant fields)
183                 // in the CollectionObject record
184                 numUpdated = updateCollectionObjectValues(collectionObjectResource,
185                         collectionObjectCsid, mostRecentMovement, resourcemap, numUpdated);
186             }
187
188         } catch (Exception e) {
189             String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
190             errMsg = errMsg + "Successfully updated " + numUpdated + " CollectionObject record(s) prior to error.";
191             logger.error(errMsg);
192             setErrorResult(errMsg);
193             getResults().setNumAffected(numUpdated);
194             return getResults();
195         }
196
197         logger.info("Updated computedCurrentLocation values in " + numUpdated + " CollectionObject record(s).");
198         getResults().setNumAffected(numUpdated);
199         return getResults();
200     }
201
202     private AbstractCommonList.ListItem getMostRecentMovement(AbstractCommonList relatedMovements) {
203         Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
204         AbstractCommonList.ListItem mostRecentMovement = null;
205         String movementCsid;
206         String currentLocation;
207         String locationDate;
208         String updateDate;
209         String mostRecentLocationDate = "";
210         String comparisonUpdateDate = "";
211         for (AbstractCommonList.ListItem movementListItem : relatedMovements.getListItem()) {
212             movementCsid = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, CSID_ELEMENT_NAME);
213             if (Tools.isBlank(movementCsid)) {
214                 continue;
215             }
216             // Skip over any duplicates in the list, such as records that might
217             // appear as the subject of one relation record and the object of
218             // its reciprocal relation record
219             if (alreadyProcessedMovementCsids.contains(movementCsid)) {
220                 continue;
221             } else {
222                 alreadyProcessedMovementCsids.add(movementCsid);
223             }
224             locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, LOCATION_DATE_ELEMENT_NAME);
225             if (Tools.isBlank(locationDate)) {
226                 continue;
227             }
228             updateDate = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, UPDATE_DATE_ELEMENT_NAME);
229             if (Tools.isBlank(updateDate)) {
230                 continue;
231             }
232             currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, CURRENT_LOCATION_ELEMENT_NAME);
233             if (Tools.isBlank(currentLocation)) {
234                 continue;
235             }
236             // Validate that this Movement record's currentLocation value parses
237             // successfully as an item refName, before identifying that record
238             // as the most recent Movement.
239             //
240             // TODO: Consider making this optional validation, in turn dependent on the
241             // value of a parameter passed in during batch job invocation.
242             if (RefNameUtils.parseAuthorityTermInfo(currentLocation) == null) {
243                 logger.warn(String.format("Could not parse current location refName '%s' in Movement record",
244                     currentLocation));
245                  continue;
246             }
247            
248             if (logger.isTraceEnabled()) {
249                 logger.trace("Location date value = " + locationDate);
250                 logger.trace("Update date value = " + updateDate);
251                 logger.trace("Current location value = " + currentLocation);
252             }
253             
254             // If this record's location date value is more recent than that of other
255             // Movement records processed so far, set the current Movement record
256             // as the most recent Movement.
257             //
258             // The following comparison assumes that all values for this element/field
259             // will be consistent ISO 8601 date/time representations, each of which can
260             // be ordered via string comparison.
261             //
262             // If this is *not* the case, we should instead parse and convert these values
263             // to date/time objects.
264             if (locationDate.compareTo(mostRecentLocationDate) > 0) {
265                 mostRecentLocationDate = locationDate;
266                 mostRecentMovement = movementListItem;
267                 comparisonUpdateDate = updateDate;
268             } else if (locationDate.compareTo(mostRecentLocationDate) == 0) {
269                 // If the two location dates match, then use a tiebreaker
270                 if (updateDate.compareTo(comparisonUpdateDate) > 0) {
271                     // The most recent location date value doesn't need to be
272                     // updated here, as the two records' values are identical
273                     mostRecentMovement = movementListItem;
274                     comparisonUpdateDate = updateDate;
275                 }
276             }
277
278         }
279         return mostRecentMovement;
280     }
281
282     // This method can be overridden and extended to update a custom set of
283     // values in the CollectionObject record by pulling in values from its
284     // most recent related Movement record.
285     //
286     // Note: any such values must first be exposed in Movement list items,
287     // in turn via configuration in Services tenant bindings ("listResultsField").
288     protected int updateCollectionObjectValues(NuxeoBasedResource collectionObjectResource,
289             String collectionObjectCsid, AbstractCommonList.ListItem mostRecentMovement,
290             ResourceMap resourcemap, int numUpdated)
291             throws DocumentException, URISyntaxException {
292         PoxPayloadOut collectionObjectPayload;
293         String computedCurrentLocation;
294         String objectNumber;
295         String previousComputedCurrentLocation;
296
297         collectionObjectPayload = findByCsid(collectionObjectResource, collectionObjectCsid);
298         if (Tools.isBlank(collectionObjectPayload.toXML())) {
299             return numUpdated;
300         } else {
301             if (logger.isTraceEnabled()) {
302                 logger.trace("Payload: " + "\n" + collectionObjectPayload);
303             }
304         }
305         // Perform the update only if the computed current location value will change
306         // as a result of the update
307         computedCurrentLocation =
308                 AbstractCommonListUtils.ListItemGetElementValue(mostRecentMovement, CURRENT_LOCATION_ELEMENT_NAME);
309         previousComputedCurrentLocation = getFieldElementValue(collectionObjectPayload,
310                 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
311                 COMPUTED_CURRENT_LOCATION_ELEMENT_NAME);
312         if (!shouldUpdateLocation(previousComputedCurrentLocation, computedCurrentLocation)) {
313             return numUpdated;
314         }
315     
316         // Perform the update only if there is a non-blank object number available.
317         //
318         // In the default CollectionObject validation handler, the object number
319         // is a required field and its (non-blank) value must be present in update
320         // payloads to successfully perform an update.
321         objectNumber = getFieldElementValue(collectionObjectPayload,
322                 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
323                 OBJECT_NUMBER_ELEMENT_NAME);
324         if (logger.isTraceEnabled()) {
325             logger.trace("Object number: " + objectNumber);
326         }
327         // FIXME: Consider making the requirement that a non-blank object number
328         // be present dependent on the value of a parameter passed in during
329         // batch job invocation, as some implementations may have turned off that
330         // validation requirement.
331         if (Tools.isBlank(objectNumber)) {
332             return numUpdated;
333         }
334
335         // At this point in the code, the most recent related Movement record
336         // should not have a null current location, as such records are
337         // excluded from consideration altogether in getMostRecentMovement().
338         // This is a redundant fallback check, in case that code somehow fails
339         // or is modified or deleted.
340         if (computedCurrentLocation == null) {
341             return numUpdated;
342         }
343
344         // Update the location.
345         String collectionObjectUpdatePayload =
346                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
347                 + "<document name=\"collectionobject\">"
348                 + "  <ns2:collectionobjects_common "
349                 + "      xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
350                 + "    <objectNumber>" + objectNumber + "</objectNumber>"
351                 + "    <computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
352                 + "  </ns2:collectionobjects_common>"
353                 + "</document>";
354         if (logger.isTraceEnabled()) {
355             logger.trace("Update payload: " + "\n" + collectionObjectUpdatePayload);
356         }
357         byte[] response = collectionObjectResource.update(resourcemap, null, collectionObjectCsid,
358                 collectionObjectUpdatePayload);
359         numUpdated++;
360         if (logger.isTraceEnabled()) {
361             logger.trace("Computed current location value for CollectionObject " + collectionObjectCsid
362                     + " was set to " + computedCurrentLocation);
363
364         }
365         return numUpdated;
366     }
367     
368     protected boolean shouldUpdateLocation(String previousLocation, String currentLocation) {
369         boolean shouldUpdate = true;
370         if (Tools.isBlank(previousLocation) && Tools.isBlank(currentLocation)) {
371             shouldUpdate = false;
372         } else if (Tools.notBlank(previousLocation) && previousLocation.equals(currentLocation)) {
373             shouldUpdate = false;
374         }
375         return shouldUpdate;
376     }
377
378     // #################################################################
379     // Ray Lee's convenience methods from his AbstractBatchJob class for the
380     // UC Berkeley Botanical Garden v2.4 implementation.
381     // #################################################################
382     protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
383         NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(serviceName);
384         return findByCsid(resource, csid);
385     }
386
387     protected PoxPayloadOut findByCsid(NuxeoBasedResource resource, String csid) throws URISyntaxException, DocumentException {
388         byte[] response = resource.get(null, createUriInfo(), csid);
389         PoxPayloadOut payload = new PoxPayloadOut(response);
390         return payload;
391     }
392
393     protected UriInfo createUriInfo() throws URISyntaxException {
394         return createUriInfo("");
395     }
396
397     private UriInfo createUriInfo(String queryString) throws URISyntaxException {
398         URI absolutePath = new URI("");
399         URI baseUri = new URI("");
400         return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
401     }
402
403     // #################################################################
404     // Other convenience methods
405     // #################################################################
406     protected UriInfo createRelatedRecordsUriInfo(String queryString) throws URISyntaxException {
407         URI uri = new URI(null, null, null, queryString, null);
408         return createUriInfo(uri.getRawQuery());
409     }
410
411     protected String getFieldElementValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
412         String value = null;
413         SAXBuilder builder = new SAXBuilder();
414         try {
415             Document document = builder.build(new StringReader(payload.toXML()));
416             Element root = document.getRootElement();
417             // The part element is always expected to have an explicit namespace.
418             Element part = root.getChild(partLabel, partNamespace);
419             // Try getting the field element both with and without a namespace.
420             // Even though a field element that lacks a namespace prefix
421             // may yet inherit its namespace from a parent, JDOM may require that
422             // the getChild() call be made without a namespace.
423             Element field = part.getChild(fieldPath, partNamespace);
424             if (field == null) {
425                 field = part.getChild(fieldPath);
426             }
427             if (field != null) {
428                 value = field.getText();
429             }
430         } catch (Exception e) {
431             logger.error("Error getting value from field path " + fieldPath
432                     + " in schema part " + partLabel);
433             return null;
434         }
435         return value;
436     }
437
438     private boolean isRecordDeleted(NuxeoBasedResource resource, String collectionObjectCsid)
439             throws URISyntaxException, DocumentException {
440         boolean isDeleted = false;
441         
442         byte[] workflowResponse = resource.getWorkflow(createUriInfo(), collectionObjectCsid);
443         if (workflowResponse != null) {
444             PoxPayloadOut payloadOut = new PoxPayloadOut(workflowResponse);
445             String workflowState =
446                     getFieldElementValue(payloadOut, WORKFLOW_COMMON_SCHEMA_NAME,
447                     WORKFLOW_COMMON_NAMESPACE, LIFECYCLE_STATE_ELEMENT_NAME);
448             if (Tools.notBlank(workflowState) && workflowState.contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
449                 isDeleted = true;
450             }
451         }
452         
453         return isDeleted;
454     }
455
456     private UriInfo addFilterToExcludeSoftDeletedRecords(UriInfo uriInfo) throws URISyntaxException {
457         if (uriInfo == null) {
458             uriInfo = createUriInfo();
459         }
460         uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_NONDELETED, Boolean.FALSE.toString());
461         return uriInfo;
462     }
463
464     private AbstractCommonList getRecordsRelatedToCsid(NuxeoBasedResource resource, String csid,
465             String relationshipDirection, boolean excludeDeletedRecords) throws URISyntaxException {
466         UriInfo uriInfo = createUriInfo();
467         uriInfo.getQueryParameters().add(relationshipDirection, csid);
468         if (excludeDeletedRecords) {
469             uriInfo = addFilterToExcludeSoftDeletedRecords(uriInfo);
470         }
471         // The 'resource' type used here identifies the record type of the
472         // related records to be retrieved
473         AbstractCommonList relatedRecords = resource.getList(uriInfo);
474         if (logger.isTraceEnabled()) {
475             logger.trace("Identified " + relatedRecords.getTotalItems()
476                     + " record(s) related to the object record via direction " + relationshipDirection + " with CSID " + csid);
477         }
478         return relatedRecords;
479     }
480
481     /**
482      * Returns the records of a specified type that are related to a specified
483      * record, where that record is the object of the relation.
484      *
485      * @param resource a resource. The type of this resource determines the type
486      * of related records that are returned.
487      * @param csid a CSID identifying a record
488      * @param excludeDeletedRecords true if 'soft-deleted' records should be
489      * excluded from results; false if those records should be included
490      * @return a list of records of a specified type, related to a specified
491      * record
492      * @throws URISyntaxException
493      */
494     private AbstractCommonList getRecordsRelatedToObjectCsid(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords) throws URISyntaxException {
495         return getRecordsRelatedToCsid(resource, csid, IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT, excludeDeletedRecords);
496     }
497
498     /**
499      * Returns the records of a specified type that are related to a specified
500      * record, where that record is the subject of the relation.
501      *
502      * @param resource a resource. The type of this resource determines the type
503      * of related records that are returned.
504      * @param csid a CSID identifying a record
505      * @param excludeDeletedRecords true if 'soft-deleted' records should be
506      * excluded from results; false if those records should be included
507      * @return a list of records of a specified type, related to a specified
508      * record
509      * @throws URISyntaxException
510      */
511     private AbstractCommonList getRecordsRelatedToSubjectCsid(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords) throws URISyntaxException {
512         return getRecordsRelatedToCsid(resource, csid, IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT, excludeDeletedRecords);
513     }
514
515     private AbstractCommonList getRelatedRecords(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords)
516             throws URISyntaxException, DocumentException {
517         AbstractCommonList relatedRecords = new AbstractCommonList();
518         AbstractCommonList recordsRelatedToObjectCSID = getRecordsRelatedToObjectCsid(resource, csid, excludeDeletedRecords);
519         AbstractCommonList recordsRelatedToSubjectCSID = getRecordsRelatedToSubjectCsid(resource, csid, excludeDeletedRecords);
520         // If either list contains any related records, merge in its items
521         if (recordsRelatedToObjectCSID.getListItem().size() > 0) {
522             relatedRecords.getListItem().addAll(recordsRelatedToObjectCSID.getListItem());
523         }
524         if (recordsRelatedToSubjectCSID.getListItem().size() > 0) {
525             relatedRecords.getListItem().addAll(recordsRelatedToSubjectCSID.getListItem());
526         }
527         if (logger.isTraceEnabled()) {
528             logger.trace("Identified a total of " + relatedRecords.getListItem().size()
529                     + " record(s) related to the record with CSID " + csid);
530         }
531         return relatedRecords;
532     }
533
534     private List<String> getCsidsList(AbstractCommonList list) {
535         List<String> csids = new ArrayList<String>();
536         for (AbstractCommonList.ListItem listitem : list.getListItem()) {
537             csids.add(AbstractCommonListUtils.ListItemGetCSID(listitem));
538         }
539         return csids;
540     }
541
542     private List<String> getMemberCsidsFromGroup(String serviceName, String groupCsid) throws URISyntaxException, DocumentException {
543         ResourceMap resourcemap = getResourceMap();
544         NuxeoBasedResource resource = (NuxeoBasedResource) resourcemap.get(serviceName);
545         return getMemberCsidsFromGroup(resource, groupCsid);
546     }
547
548     private List<String> getMemberCsidsFromGroup(NuxeoBasedResource resource, String groupCsid) throws URISyntaxException, DocumentException {
549         // The 'resource' type used here identifies the record type of the
550         // related records to be retrieved
551         AbstractCommonList relatedRecords =
552                 getRelatedRecords(resource, groupCsid, EXCLUDE_DELETED);
553         List<String> memberCsids = getCsidsList(relatedRecords);
554         return memberCsids;
555     }
556
557     private List<String> getNoContextCsids() throws URISyntaxException {
558         ResourceMap resourcemap = getResourceMap();
559         NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
560         UriInfo uriInfo = createUriInfo();
561         uriInfo = addFilterToExcludeSoftDeletedRecords(uriInfo);
562         AbstractCommonList collectionObjects = collectionObjectResource.getList(uriInfo);
563         List<String> noContextCsids = getCsidsList(collectionObjects);
564         return noContextCsids;
565     }
566 }