1 package org.collectionspace.services.batch.nuxeo;
3 import java.io.StringReader;
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;
13 import javax.ws.rs.core.PathSegment;
14 import javax.ws.rs.core.UriInfo;
16 import org.collectionspace.services.batch.AbstractBatchInvocable;
17 import org.collectionspace.services.batch.BatchCommon;
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.invocable.InvocationResults;
30 import org.collectionspace.services.common.query.UriInfoImpl;
31 import org.collectionspace.services.jaxb.AbstractCommonList;
32 import org.dom4j.DocumentException;
33 //import org.jboss.resteasy.specimpl.UriInfoImpl;
34 import org.jdom.Document;
35 import org.jdom.Element;
36 import org.jdom.Namespace;
37 import org.jdom.input.SAXBuilder;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
43 // FIXME: Where appropriate, get from existing constants rather than local declarations
44 private final static String COMPUTED_CURRENT_LOCATION_ELEMENT_NAME = "computedCurrentLocation";
45 private final static String CSID_ELEMENT_NAME = "csid";
46 private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation";
47 private final static String LIFECYCLE_STATE_ELEMENT_NAME = "currentLifeCycleState";
48 private final static String LOCATION_DATE_ELEMENT_NAME = "locationDate";
49 private final static String OBJECT_NUMBER_ELEMENT_NAME = "objectNumber";
50 private final static String UPDATE_DATE_ELEMENT_NAME = "updatedAt";
51 private final static String WORKFLOW_COMMON_SCHEMA_NAME = "workflow_common";
52 private final static String WORKFLOW_COMMON_NAMESPACE_PREFIX = "ns2";
53 private final static String WORKFLOW_COMMON_NAMESPACE_URI =
54 "http://collectionspace.org/services/workflow";
55 private final static Namespace WORKFLOW_COMMON_NAMESPACE =
56 Namespace.getNamespace(
57 WORKFLOW_COMMON_NAMESPACE_PREFIX,
58 WORKFLOW_COMMON_NAMESPACE_URI);
59 private final static String COLLECTIONOBJECTS_COMMON_SCHEMA_NAME = "collectionobjects_common";
60 private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX = "ns2";
61 private final static String COLLECTIONOBJECTS_COMMON_NAMESPACE_URI =
62 "http://collectionspace.org/services/collectionobject";
63 private final static Namespace COLLECTIONOBJECTS_COMMON_NAMESPACE =
64 Namespace.getNamespace(
65 COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX,
66 COLLECTIONOBJECTS_COMMON_NAMESPACE_URI);
67 private static final int DEFAULT_PAGE_SIZE = 1000;
68 private final boolean EXCLUDE_DELETED = true;
69 private final String CLASSNAME = this.getClass().getSimpleName();
70 private final Logger logger = LoggerFactory.getLogger(this.getClass());
72 // Initialization tasks
73 public UpdateObjectLocationBatchJob() {
74 setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE, INVOCATION_MODE_LIST,
75 INVOCATION_MODE_GROUP, INVOCATION_MODE_NO_CONTEXT));
79 public void run(BatchCommon batchCommon) {
80 String errMsg = String.format("%s class does not support run(BatchCommon batchCommon) method.", getClass().getName());
81 throw new java.lang.UnsupportedOperationException(errMsg);
85 * The main work logic of the batch job. Will be called after setContext.
90 setCompletionStatus(STATUS_MIN_PROGRESS);
94 List<String> csids = new ArrayList<String>();
96 // Build a list of CollectionObject records to process via this
97 // batch job, depending on the invocation mode requested.
98 if (requestIsForInvocationModeSingle()) {
99 String singleCsid = getInvocationContext().getSingleCSID();
100 if (Tools.isBlank(singleCsid)) {
101 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
103 csids.add(singleCsid);
105 } else if (requestIsForInvocationModeList()) {
106 List<String> listCsids = getListCsids();
107 if (listCsids.isEmpty()) {
108 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
110 csids.addAll(listCsids);
111 } else if (requestIsForInvocationModeGroup()) {
112 String groupCsid = getInvocationContext().getGroupCSID();
113 if (Tools.isBlank(groupCsid)) {
114 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
116 List<String> groupMemberCsids = getMemberCsidsFromGroup(CollectionObjectClient.SERVICE_NAME, groupCsid);
117 if (groupMemberCsids.isEmpty()) {
118 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
120 csids.addAll(groupMemberCsids);
121 } else if (requestIsForInvocationModeNoContext()) {
122 List<String> noContextCsids = getNoContextCsids();
123 if (noContextCsids.isEmpty()) {
124 throw new Exception(CSID_VALUES_NOT_PROVIDED_IN_INVOCATION_CONTEXT);
126 csids.addAll(noContextCsids);
128 if (logger.isInfoEnabled()) {
129 logger.info("Identified " + csids.size() + " total CollectionObject(s) to be processed via the " + CLASSNAME + " batch job");
132 // Update the value of the computed current location field for each CollectionObject
133 setResults(updateComputedCurrentLocations(csids));
134 setCompletionStatus(STATUS_COMPLETE);
136 } catch (Exception e) {
137 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage();
138 setErrorResult(errMsg);
143 private InvocationResults updateComputedCurrentLocations(List<String> csids) {
144 ResourceMap resourcemap = getResourceMap();
145 NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
146 NuxeoBasedResource movementResource = (NuxeoBasedResource) resourcemap.get(MovementClient.SERVICE_NAME);
150 long recordsToProcess = csids.size();
151 long logInterval = recordsToProcess / 10 + 2;
154 // For each CollectionObject record
155 for (String collectionObjectCsid : csids) {
157 // Log progress at INFO level
158 if (processed % logInterval == 0) {
159 logger.info(String.format("Recalculated computed location for %d of %d cataloging records.",
160 processed, recordsToProcess));
164 // Skip over soft-deleted CollectionObject records
166 // (Invocations using the 'no context' mode have already
167 // filtered out soft-deleted records.)
168 if (!requestIsForInvocationModeNoContext()) {
169 if (isRecordDeleted(collectionObjectResource, collectionObjectCsid)) {
170 if (logger.isTraceEnabled()) {
171 logger.trace("Skipping soft-deleted CollectionObject record with CSID " + collectionObjectCsid);
176 // Get the Movement records related to this CollectionObject record
177 AbstractCommonList relatedMovements =
178 getRelatedRecords(movementResource, collectionObjectCsid, EXCLUDE_DELETED);
179 // Skip over CollectionObject records that have no related Movement records
180 if (relatedMovements.getListItem().isEmpty()) {
183 // Get the most recent 'suitable' Movement record, one which
184 // contains both a location date and a current location value
185 AbstractCommonList.ListItem mostRecentMovement = getMostRecentMovement(relatedMovements);
186 // Skip over CollectionObject records where no suitable
187 // most recent Movement record can be identified.
189 // FIXME: Clarify: it ever necessary to 'unset' a computed
190 // current location value, by setting it to a null or empty value,
191 // if that value is no longer obtainable from related Movement
193 if (mostRecentMovement == null) {
196 // Update the value of the computed current location field
197 // (and, via subclasses, this and/or other relevant fields)
198 // in the CollectionObject record
199 numUpdated = updateCollectionObjectValues(collectionObjectResource,
200 collectionObjectCsid, mostRecentMovement, resourcemap, numUpdated);
203 } catch (Exception e) {
204 String errMsg = "Error encountered in " + CLASSNAME + ": " + e.getLocalizedMessage() + " ";
205 errMsg = errMsg + "Successfully updated " + numUpdated + " CollectionObject record(s) prior to error.";
206 logger.error(errMsg);
207 setErrorResult(errMsg);
208 getResults().setNumAffected(numUpdated);
212 logger.info("Updated computedCurrentLocation values in " + numUpdated + " CollectionObject record(s).");
213 getResults().setNumAffected(numUpdated);
218 // Returns the number of distinct/unique CSID values in the list
220 private int getNumberOfDistinceRecords(AbstractCommonList abstractCommonList) {
221 Set<String> resultSet = new HashSet<String>();
223 for (AbstractCommonList.ListItem listItem : abstractCommonList.getListItem()) {
224 String csid = AbstractCommonListUtils.ListItemGetElementValue(listItem, CSID_ELEMENT_NAME);
225 if (!Tools.isBlank(csid)) {
230 return resultSet.size();
233 private AbstractCommonList.ListItem getMostRecentMovement(AbstractCommonList relatedMovements) {
234 Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
235 AbstractCommonList.ListItem mostRecentMovement = null;
237 String currentLocation;
240 String mostRecentLocationDate = "";
241 String comparisonUpdateDate = "";
244 // If there is only one related movement record, then return it as the most recent
245 // movement record -if it's current location element is not empty.
247 if (getNumberOfDistinceRecords(relatedMovements) == 1) {
248 mostRecentMovement = relatedMovements.getListItem().get(0);
249 currentLocation = AbstractCommonListUtils.ListItemGetElementValue(mostRecentMovement, CURRENT_LOCATION_ELEMENT_NAME);
250 if (Tools.isBlank(currentLocation)) {
251 mostRecentMovement = null;
253 return mostRecentMovement;
256 for (AbstractCommonList.ListItem movementListItem : relatedMovements.getListItem()) {
257 movementCsid = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, CSID_ELEMENT_NAME);
258 if (Tools.isBlank(movementCsid)) {
261 // Skip over any duplicates in the list, such as records that might
262 // appear as the subject of one relation record and the object of
263 // its reciprocal relation record
264 if (alreadyProcessedMovementCsids.contains(movementCsid)) {
267 alreadyProcessedMovementCsids.add(movementCsid);
269 locationDate = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, LOCATION_DATE_ELEMENT_NAME);
270 if (Tools.isBlank(locationDate)) {
273 updateDate = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, UPDATE_DATE_ELEMENT_NAME);
274 if (Tools.isBlank(updateDate)) {
277 currentLocation = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, CURRENT_LOCATION_ELEMENT_NAME);
278 if (Tools.isBlank(currentLocation)) {
281 // Validate that this Movement record's currentLocation value parses
282 // successfully as an item refName, before identifying that record
283 // as the most recent Movement.
285 // TODO: Consider making this optional validation, in turn dependent on the
286 // value of a parameter passed in during batch job invocation.
287 if (RefNameUtils.parseAuthorityTermInfo(currentLocation) == null) {
288 logger.warn(String.format("Could not parse current location refName '%s' in Movement record",
293 if (logger.isTraceEnabled()) {
294 logger.trace("Location date value = " + locationDate);
295 logger.trace("Update date value = " + updateDate);
296 logger.trace("Current location value = " + currentLocation);
299 // If this record's location date value is more recent than that of other
300 // Movement records processed so far, set the current Movement record
301 // as the most recent Movement.
303 // The following comparison assumes that all values for this element/field
304 // will be consistent ISO 8601 date/time representations, each of which can
305 // be ordered via string comparison.
307 // If this is *not* the case, we should instead parse and convert these values
308 // to date/time objects.
309 if (locationDate.compareTo(mostRecentLocationDate) > 0) {
310 mostRecentLocationDate = locationDate;
311 mostRecentMovement = movementListItem;
312 comparisonUpdateDate = updateDate;
313 } else if (locationDate.compareTo(mostRecentLocationDate) == 0) {
314 // If the two location dates match, then use a tiebreaker
315 if (updateDate.compareTo(comparisonUpdateDate) > 0) {
316 // The most recent location date value doesn't need to be
317 // updated here, as the two records' values are identical
318 mostRecentMovement = movementListItem;
319 comparisonUpdateDate = updateDate;
325 return mostRecentMovement;
328 // This method can be overridden and extended to update a custom set of
329 // values in the CollectionObject record by pulling in values from its
330 // most recent related Movement record.
332 // Note: any such values must first be exposed in Movement list items,
333 // in turn via configuration in Services tenant bindings ("listResultsField").
334 protected long updateCollectionObjectValues(NuxeoBasedResource collectionObjectResource,
335 String collectionObjectCsid,
336 AbstractCommonList.ListItem mostRecentMovement,
337 ResourceMap resourcemap, long numUpdated)
338 throws DocumentException, URISyntaxException {
339 PoxPayloadOut collectionObjectPayload;
340 String computedCurrentLocation;
342 String previousComputedCurrentLocation;
344 collectionObjectPayload = findByCsid(collectionObjectResource, collectionObjectCsid);
345 if (Tools.isBlank(collectionObjectPayload.toXML())) {
348 if (logger.isTraceEnabled()) {
349 logger.trace("Payload: " + "\n" + collectionObjectPayload);
352 // Perform the update only if the computed current location value will change
353 // as a result of the update
354 computedCurrentLocation =
355 AbstractCommonListUtils.ListItemGetElementValue(mostRecentMovement, CURRENT_LOCATION_ELEMENT_NAME);
356 previousComputedCurrentLocation = getFieldElementValue(collectionObjectPayload,
357 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
358 COMPUTED_CURRENT_LOCATION_ELEMENT_NAME);
359 if (!shouldUpdateLocation(previousComputedCurrentLocation, computedCurrentLocation)) {
363 // Perform the update only if there is a non-blank object number available.
365 // In the default CollectionObject validation handler, the object number
366 // is a required field and its (non-blank) value must be present in update
367 // payloads to successfully perform an update.
368 objectNumber = getFieldElementValue(collectionObjectPayload,
369 COLLECTIONOBJECTS_COMMON_SCHEMA_NAME, COLLECTIONOBJECTS_COMMON_NAMESPACE,
370 OBJECT_NUMBER_ELEMENT_NAME);
371 if (logger.isTraceEnabled()) {
372 logger.trace("Object number: " + objectNumber);
374 // FIXME: Consider making the requirement that a non-blank object number
375 // be present dependent on the value of a parameter passed in during
376 // batch job invocation, as some implementations may have turned off that
377 // validation requirement.
378 if (Tools.isBlank(objectNumber)) {
382 // At this point in the code, the most recent related Movement record
383 // should not have a null current location, as such records are
384 // excluded from consideration altogether in getMostRecentMovement().
385 // This is a redundant fallback check, in case that code somehow fails
386 // or is modified or deleted.
387 if (computedCurrentLocation == null) {
391 // Update the location.
392 String collectionObjectUpdatePayload =
393 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
394 + "<document name=\"collectionobject\">"
395 + " <ns2:collectionobjects_common "
396 + " xmlns:ns2=\"http://collectionspace.org/services/collectionobject\">"
397 + " <objectNumber>" + objectNumber + "</objectNumber>"
398 + " <computedCurrentLocation>" + computedCurrentLocation + "</computedCurrentLocation>"
399 + " </ns2:collectionobjects_common>"
401 if (logger.isTraceEnabled()) {
402 logger.trace("Update payload: " + "\n" + collectionObjectUpdatePayload);
406 // Update the record and save the response for debugging message
408 UriInfo uriInfo = this.setupQueryParamForUpdateRecords(); // Determines if we'll updated the updateAt and updatedBy core values
409 byte[] responseBytes = collectionObjectResource.update(getServiceContext(), resourcemap, uriInfo, collectionObjectCsid,
410 collectionObjectUpdatePayload);
413 if (logger.isDebugEnabled()) {
414 logger.debug(String.format("Batch resource: Resonse from collectionobject (cataloging record) update: %s", new String(responseBytes)));
417 if (logger.isTraceEnabled()) {
418 logger.trace("Computed current location value for CollectionObject " + collectionObjectCsid
419 + " was set to " + computedCurrentLocation);
425 protected boolean shouldUpdateLocation(String previousLocation, String currentLocation) {
426 boolean shouldUpdate = true;
427 if (Tools.isBlank(previousLocation) && Tools.isBlank(currentLocation)) {
428 shouldUpdate = false;
429 } else if (Tools.notBlank(previousLocation) && previousLocation.equals(currentLocation)) {
430 shouldUpdate = false;
435 // #################################################################
436 // Ray Lee's convenience methods from his AbstractBatchJob class for the
437 // UC Berkeley Botanical Garden v2.4 implementation.
438 // #################################################################
439 protected PoxPayloadOut findByCsid(String serviceName, String csid) throws URISyntaxException, DocumentException {
440 NuxeoBasedResource resource = (NuxeoBasedResource) getResourceMap().get(serviceName);
441 return findByCsid(resource, csid);
444 protected PoxPayloadOut findByCsid(NuxeoBasedResource resource, String csid) throws URISyntaxException, DocumentException {
445 PoxPayloadOut result = null;
448 result = resource.getWithParentCtx(getServiceContext(), csid);
449 } catch (Exception e) {
450 String msg = String.format("UpdateObjectLocation batch job could find/get resource CSID='%s' of type '%s'",
451 csid, resource.getServiceName());
452 if (logger.isDebugEnabled()) {
453 logger.debug(msg, e);
462 protected UriInfo createUriInfo() throws URISyntaxException {
463 return createUriInfo("");
466 private UriInfo createUriInfo(String queryString) throws URISyntaxException {
467 URI absolutePath = new URI("");
468 URI baseUri = new URI("");
469 return new UriInfoImpl(absolutePath, baseUri, "", queryString, Collections.<PathSegment>emptyList());
472 // #################################################################
473 // Other convenience methods
474 // #################################################################
475 protected UriInfo createRelatedRecordsUriInfo(String queryString) throws URISyntaxException {
476 URI uri = new URI(null, null, null, queryString, null);
477 return createUriInfo(uri.getRawQuery());
480 protected UriInfo setupQueryParamForUpdateRecords() throws URISyntaxException {
481 UriInfo result = null;
484 // Check first to see if we've got a query param. It will override any invocation context value
486 String updateCoreValues = (String) getServiceContext().getQueryParams().getFirst(IClientQueryParams.UPDATE_CORE_VALUES);
487 if (Tools.isBlank(updateCoreValues)) {
489 // Since there is no query param, let's check the invocation context
491 updateCoreValues = getInvocationContext().getUpdateCoreValues();
495 // If we found a value, then use it to create a query parameter
497 if (Tools.notBlank(updateCoreValues)) {
498 result = createUriInfo(IClientQueryParams.UPDATE_CORE_VALUES + "=" + updateCoreValues);
504 protected String getFieldElementValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
506 SAXBuilder builder = new SAXBuilder();
508 Document document = builder.build(new StringReader(payload.toXML()));
509 Element root = document.getRootElement();
510 // The part element is always expected to have an explicit namespace.
511 Element part = root.getChild(partLabel, partNamespace);
512 // Try getting the field element both with and without a namespace.
513 // Even though a field element that lacks a namespace prefix
514 // may yet inherit its namespace from a parent, JDOM may require that
515 // the getChild() call be made without a namespace.
516 Element field = part.getChild(fieldPath, partNamespace);
518 field = part.getChild(fieldPath);
521 value = field.getText();
523 } catch (Exception e) {
524 logger.error("Error getting value from field path " + fieldPath
525 + " in schema part " + partLabel);
531 private boolean isRecordDeleted(NuxeoBasedResource resource, String collectionObjectCsid)
532 throws URISyntaxException, DocumentException {
533 boolean isDeleted = false;
535 byte[] workflowResponse = resource.getWorkflowWithExistingContext(getServiceContext(), createUriInfo(), collectionObjectCsid);
536 if (workflowResponse != null) {
537 PoxPayloadOut payloadOut = new PoxPayloadOut(workflowResponse);
538 String workflowState =
539 getFieldElementValue(payloadOut, WORKFLOW_COMMON_SCHEMA_NAME,
540 WORKFLOW_COMMON_NAMESPACE, LIFECYCLE_STATE_ELEMENT_NAME);
541 if (Tools.notBlank(workflowState) && workflowState.contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
549 private UriInfo addFilterToExcludeSoftDeletedRecords(UriInfo uriInfo) throws URISyntaxException {
550 if (uriInfo == null) {
551 uriInfo = createUriInfo();
553 uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_DELETED_QP, Boolean.FALSE.toString());
557 private UriInfo addFilterForPageSize(UriInfo uriInfo, long startPage, long pageSize) throws URISyntaxException {
558 if (uriInfo == null) {
559 uriInfo = createUriInfo();
561 uriInfo.getQueryParameters().addFirst(IClientQueryParams.START_PAGE_PARAM, Long.toString(startPage));
562 uriInfo.getQueryParameters().addFirst(IClientQueryParams.PAGE_SIZE_PARAM, Long.toString(pageSize));
567 private AbstractCommonList getRecordsRelatedToCsid(NuxeoBasedResource resource, String csid,
568 String relationshipDirection, boolean excludeDeletedRecords) throws URISyntaxException {
569 UriInfo uriInfo = createUriInfo();
570 uriInfo.getQueryParameters().add(relationshipDirection, csid);
571 if (excludeDeletedRecords) {
572 uriInfo = addFilterToExcludeSoftDeletedRecords(uriInfo);
574 // The 'resource' type used here identifies the record type of the
575 // related records to be retrieved
576 AbstractCommonList relatedRecords = resource.getList(getServiceContext(), uriInfo);
577 if (logger.isTraceEnabled()) {
578 logger.trace("Identified " + relatedRecords.getTotalItems()
579 + " record(s) related to the object record via direction " + relationshipDirection + " with CSID " + csid);
581 return relatedRecords;
585 * Returns the records of a specified type that are related to a specified
586 * record, where that record is the object of the relation.
588 * @param resource a resource. The type of this resource determines the type
589 * of related records that are returned.
590 * @param csid a CSID identifying a record
591 * @param excludeDeletedRecords true if 'soft-deleted' records should be
592 * excluded from results; false if those records should be included
593 * @return a list of records of a specified type, related to a specified
595 * @throws URISyntaxException
597 private AbstractCommonList getRecordsRelatedToObjectCsid(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords) throws URISyntaxException {
598 return getRecordsRelatedToCsid(resource, csid, IQueryManager.SEARCH_RELATED_TO_CSID_AS_OBJECT, excludeDeletedRecords);
602 * Returns the records of a specified type that are related to a specified
603 * record, where that record is the subject of the relation.
605 * @param resource a resource. The type of this resource determines the type
606 * of related records that are returned.
607 * @param csid a CSID identifying a record
608 * @param excludeDeletedRecords true if 'soft-deleted' records should be
609 * excluded from results; false if those records should be included
610 * @return a list of records of a specified type, related to a specified
612 * @throws URISyntaxException
614 private AbstractCommonList getRecordsRelatedToSubjectCsid(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords) throws URISyntaxException {
615 return getRecordsRelatedToCsid(resource, csid, IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT, excludeDeletedRecords);
618 private AbstractCommonList getRelatedRecords(NuxeoBasedResource resource, String csid, boolean excludeDeletedRecords)
619 throws URISyntaxException, DocumentException {
620 AbstractCommonList relatedRecords = new AbstractCommonList();
621 AbstractCommonList recordsRelatedToObjectCSID = getRecordsRelatedToObjectCsid(resource, csid, excludeDeletedRecords);
622 AbstractCommonList recordsRelatedToSubjectCSID = getRecordsRelatedToSubjectCsid(resource, csid, excludeDeletedRecords);
623 // If either list contains any related records, merge in its items
624 if (recordsRelatedToObjectCSID.getListItem().size() > 0) {
625 relatedRecords.getListItem().addAll(recordsRelatedToObjectCSID.getListItem());
627 if (recordsRelatedToSubjectCSID.getListItem().size() > 0) {
628 relatedRecords.getListItem().addAll(recordsRelatedToSubjectCSID.getListItem());
630 if (logger.isTraceEnabled()) {
631 logger.trace("Identified a total of " + relatedRecords.getListItem().size()
632 + " record(s) related to the record with CSID " + csid);
634 return relatedRecords;
637 private List<String> getCsidsList(AbstractCommonList list) {
638 List<String> csids = new ArrayList<String>();
639 for (AbstractCommonList.ListItem listitem : list.getListItem()) {
640 csids.add(AbstractCommonListUtils.ListItemGetCSID(listitem));
645 private void appendItemsToCsidsList(List<String> existingList, AbstractCommonList abstractCommonList) {
646 for (AbstractCommonList.ListItem listitem : abstractCommonList.getListItem()) {
647 existingList.add(AbstractCommonListUtils.ListItemGetCSID(listitem));
651 private List<String> getMemberCsidsFromGroup(String serviceName, String groupCsid) throws URISyntaxException, DocumentException {
652 ResourceMap resourcemap = getResourceMap();
653 NuxeoBasedResource resource = (NuxeoBasedResource) resourcemap.get(serviceName);
654 return getMemberCsidsFromGroup(resource, groupCsid);
657 private List<String> getMemberCsidsFromGroup(NuxeoBasedResource resource, String groupCsid) throws URISyntaxException, DocumentException {
658 // The 'resource' type used here identifies the record type of the
659 // related records to be retrieved
660 AbstractCommonList relatedRecords =
661 getRelatedRecords(resource, groupCsid, EXCLUDE_DELETED);
662 List<String> memberCsids = getCsidsList(relatedRecords);
666 private List<String> getNoContextCsids() throws URISyntaxException {
667 ResourceMap resourcemap = getResourceMap();
668 NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
669 UriInfo uriInfo = createUriInfo();
670 uriInfo = addFilterToExcludeSoftDeletedRecords(uriInfo);
672 boolean morePages = true;
673 long currentPage = 0;
674 long pageSize = DEFAULT_PAGE_SIZE;
675 List<String> noContextCsids = new ArrayList<String>();
677 while (morePages == true) {
678 uriInfo = addFilterForPageSize(uriInfo, currentPage, pageSize);
679 AbstractCommonList collectionObjects = collectionObjectResource.getList(getServiceContext(), uriInfo);
680 appendItemsToCsidsList(noContextCsids, collectionObjects);
682 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.
689 return noContextCsids;