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