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