1 package org.collectionspace.services.listener;
3 import java.util.GregorianCalendar;
4 import java.util.HashSet;
7 import org.collectionspace.services.client.LocationAuthorityClient;
8 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants;
9 import org.collectionspace.services.common.api.Tools;
10 import org.collectionspace.services.common.document.DocumentException;
11 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
12 import org.collectionspace.services.common.api.RefName;
13 import org.collectionspace.services.movement.nuxeo.MovementConstants;
14 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
15 import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper;
16 import org.collectionspace.services.nuxeo.listener.AbstractCSEventSyncListenerImpl;
17 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
19 import org.nuxeo.ecm.core.api.ClientException;
20 import org.nuxeo.ecm.core.api.DocumentModel;
21 import org.nuxeo.ecm.core.api.DocumentModelList;
22 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
23 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
24 import org.nuxeo.ecm.core.event.Event;
25 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
27 public abstract class AbstractUpdateObjectLocationValues extends AbstractCSEventSyncListenerImpl {
28 // FIXME: Make the following message, or its equivalent, a constant usable by all event listeners
29 private final static String NO_FURTHER_PROCESSING_MESSAGE =
30 "This event listener will not continue processing this event ...";
32 private final static GregorianCalendar EARLIEST_COMPARISON_DATE = new GregorianCalendar(1600, 1, 1);
33 private final static String RELATIONS_COMMON_SCHEMA = "relations_common"; // FIXME: Get from external constant
35 private final static String COLLECTIONOBJECT_DOCTYPE = CollectionObjectConstants.NUXEO_DOCTYPE;
36 private final static String RELATION_DOCTYPE = RelationConstants.NUXEO_DOCTYPE;//"Relation"; // FIXME: Get from external constant
37 private final static String MOVEMENT_DOCTYPE = MovementConstants.NUXEO_DOCTYPE;
39 private final static String SUBJECT_CSID_PROPERTY = "subjectCsid"; // FIXME: Get from external constant
40 private final static String OBJECT_CSID_PROPERTY = "objectCsid"; // FIXME: Get from external constant
41 private final static String SUBJECT_DOCTYPE_PROPERTY = "subjectDocumentType"; // FIXME: Get from external constant
42 private final static String OBJECT_DOCTYPE_PROPERTY = "objectDocumentType"; // FIXME: Get from external constant
43 protected final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common"; // FIXME: Get from external constant
44 protected final static String COMPUTED_CURRENT_LOCATION_PROPERTY = "computedCurrentLocation"; // FIXME: Create and then get from external constant
45 private final static String CURRENT_LOCATION_ELEMENT_NAME = "currentLocation"; // From movement_commons schema. FIXME: Get from external constant that already exists
46 protected final static String MOVEMENTS_COMMON_SCHEMA = "movements_common"; // FIXME: Get from external constant
47 private final static String LOCATION_DATE_PROPERTY = "locationDate"; // FIXME: Get from external constant
48 protected final static String CURRENT_LOCATION_PROPERTY = "currentLocation"; // FIXME: Get from external constant
49 protected final static String COLLECTIONSPACE_CORE_SCHEMA = "collectionspace_core"; // FIXME: Get from external constant
50 protected final static String CREATED_AT_PROPERTY = "createdAt"; // FIXME: Get from external constant
51 protected final static String UPDATED_AT_PROPERTY = "updatedAt"; // FIXME: Get from external constant
53 // Use this meta URN/refname to mark computed locations that are indeterminate
54 private final static String INDETERMINATE_ID = "indeterminate";
55 protected final static String INDETERMINATE_LOCATION = RefName.buildAuthorityItem(INDETERMINATE_ID, LocationAuthorityClient.SERVICE_NAME, INDETERMINATE_ID,
56 INDETERMINATE_ID, "~Indeterminate Location~").toString();
58 // Used to set/get temp values in a DocumentModel instance
59 private static final String IGNORE_LOCATION_UPDATE_EVENT_LABEL = "IGNORE_LOCATION_UPDATE_EVENT";
61 public enum EventNotificationDocumentType {
62 // Document type about which we've received a notification
63 MOVEMENT, RELATION, COLLECTIONOBJECT;
66 public boolean shouldHandleEvent(Event event) {
67 // Ensure we have all the event data we need to proceed.
68 if (event.getContext() instanceof DocumentEventContext == false) {
72 // This event handler itself sometimes triggers additional events. To prevent unnecessary cascading event handling, this event
73 // handler sets a temp flag in the document model's context indicating we should ignore cascading events. This method checks that flag and
74 // exits if it is set.
75 DocumentEventContext docEventContext = (DocumentEventContext) event.getContext();
76 Boolean shouldIgnoreEvent = (Boolean) getContextPropertyValue(docEventContext, IGNORE_LOCATION_UPDATE_EVENT_LABEL);
77 if (shouldIgnoreEvent != null && shouldIgnoreEvent) {
85 public void handleCSEvent(Event event) {
86 DocumentEventContext docEventContext = (DocumentEventContext) event.getContext();
87 DocumentModel eventDocModel = docEventContext.getSourceDocument();
88 String eventType = event.getName();
89 boolean isAboutToBeRemovedEvent = eventType.equals(DocumentEventTypes.ABOUT_TO_REMOVE);
92 // Ensure this event relates to a relationship record (between cataloging and movement records) or a movement record. If so, get the CSID
93 // of the corresponding movement record. Otherwise, exit.
95 String eventMovementCsid = null;
96 Enum<EventNotificationDocumentType> notificationDocumentType;
97 if (documentMatchesType(eventDocModel, RELATION_DOCTYPE)) {
98 notificationDocumentType = EventNotificationDocumentType.RELATION;
99 // Ensure this relationship record is a CollectionObject/Movement tuple.
100 eventMovementCsid = getCsidForDesiredDocTypeFromRelation(eventDocModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
101 if (Tools.isBlank(eventMovementCsid)) {
104 } else if (documentMatchesType(eventDocModel, MOVEMENT_DOCTYPE)) {
105 notificationDocumentType = EventNotificationDocumentType.MOVEMENT;
106 // Otherwise, get a Movement CSID directly from the Movement record.
107 eventMovementCsid = NuxeoUtils.getCsid(eventDocModel);
108 if (Tools.isBlank(eventMovementCsid)) {
109 getLogger().warn("Could not obtain CSID for Movement record from document event.");
110 getLogger().warn(NO_FURTHER_PROCESSING_MESSAGE);
113 } else if (documentMatchesType(eventDocModel, COLLECTIONOBJECT_DOCTYPE) &&
114 eventType.equals(DocumentEventTypes.DOCUMENT_UPDATED)) {
115 notificationDocumentType = EventNotificationDocumentType.COLLECTIONOBJECT;
117 // We don't need to handle this event.
121 // Note: currently, all Document lifecycle transitions on
122 // the relevant doctype(s) are handled by this event handler,
123 // not just transitions between 'soft deleted' and active states.
125 // We are assuming that we'll want to re-compute current locations
126 // for related CollectionObjects on all such transitions, as the
127 // semantics of such transitions are opaque to this event handler,
128 // because arbitrary workflows can be bound to those doctype(s).
130 // If we need to filter out some of those lifecycle transitions,
131 // such as excluding transitions to the 'locked' workflow state; or,
132 // alternately, if we want to restrict this event handler's
133 // scope to handle only transitions into the 'soft deleted' state,
134 // we can add additional checks for doing so at this point in the code.
137 // Get a list of all the CollectionObject records affected by this event.
139 CoreSessionInterface session = new CoreSessionWrapper(docEventContext.getCoreSession()); // NOTE: All Nuxeo sessions that get passed around to CollectionSpace code need to be wrapped inside of a CoreSessionWrapper
140 Set<String> collectionObjectCsids = new HashSet<>();
141 if (notificationDocumentType == EventNotificationDocumentType.RELATION) {
142 String relatedCollectionObjectCsid = getCsidForDesiredDocTypeFromRelation(eventDocModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
143 collectionObjectCsids.add(relatedCollectionObjectCsid);
144 } else if (notificationDocumentType == EventNotificationDocumentType.MOVEMENT) {
145 collectionObjectCsids.addAll(getCollectionObjectCsidsRelatedToMovement(eventMovementCsid, session));
146 } else if (notificationDocumentType == EventNotificationDocumentType.COLLECTIONOBJECT) {
147 collectionObjectCsids.add(NuxeoUtils.getCsid(eventDocModel));
149 // This event did not involve a document relevant to us.
154 // If we found no collectionobject records needing updating, then we're done.
156 if (collectionObjectCsids.isEmpty() == true) {
161 // Now iterate through the list of affected CollectionObjects found.
162 // For each CollectionObject, obtain its most recent, related Movement record,
163 // and update update the Computed Current Location field if needed.
165 DocumentModel collectionObjectDocModel;
166 for (String collectionObjectCsid : collectionObjectCsids) {
167 collectionObjectDocModel = getCurrentDocModelFromCsid(session, collectionObjectCsid);
168 if (isActiveDocument(collectionObjectDocModel) == true) {
169 DocumentModel movementDocModel = getCurrentDocModelFromCsid(session, eventMovementCsid);
171 // Get the CollectionObject's most recent, valid related Movement to use for computing the
172 // object's current location.
174 String mostRecentLocation = getMostRecentLocation(event, session, collectionObjectCsid,
175 isAboutToBeRemovedEvent, eventMovementCsid);
177 // Update the CollectionObject's Computed Current Location field with the Movement record's location
179 boolean didLocationChange = updateCollectionObjectLocation(collectionObjectDocModel, movementDocModel, mostRecentLocation);
182 // If the location changed, save/persist the change to the repository and log the change.
184 if (didLocationChange == true) {
185 persistLocationChange(session, collectionObjectDocModel);
187 // Log an INFO message if we've changed the cataloging record's location
189 if (getLogger().isInfoEnabled()) {
190 String computedCurrentLocationRefName =
191 (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY);
192 getLogger().info(String.format("Updating cataloging record=%s current location to %s",
193 NuxeoUtils.getCsid(collectionObjectDocModel), computedCurrentLocationRefName));
201 // Disable update/documentModified events and persist the location change.
203 private void persistLocationChange(CoreSessionInterface session, DocumentModel collectionObjectDocModel) {
206 // Set a flag in the document model indicating that we want to ignore the update event that
207 // will be triggered by this save/persist request.
208 setDocModelContextProperty(collectionObjectDocModel, IGNORE_LOCATION_UPDATE_EVENT_LABEL, true);
211 // Save/Persist the document to the DB
212 session.saveDocument(collectionObjectDocModel);
215 // Clear the flag we set to ignore events triggered by our save request.
216 clearDocModelContextProperty(collectionObjectDocModel, IGNORE_LOCATION_UPDATE_EVENT_LABEL);
220 * Returns the CSIDs of active CollectionObject records related to a Movement record.
222 * @param movementCsid the CSID of a Movement record.
223 * @param coreSession a repository session.
224 * @throws ClientException
225 * @return the CSIDs of the CollectionObject records, if any, which are
226 * related to the Movement record.
227 * @throws DocumentException
229 private Set<String> getCollectionObjectCsidsRelatedToMovement(String movementCsid,
230 CoreSessionInterface coreSession) throws ClientException {
232 Set<String> csids = new HashSet<>();
234 // Via an NXQL query, get a list of active relation records where:
235 // * This movement record's CSID is the subject CSID of the relation,
236 // and its object document type is a CollectionObject doctype;
238 // * This movement record's CSID is the object CSID of the relation,
239 // and its subject document type is a CollectionObject doctype.
241 // Some values below are hard-coded for readability, rather than
242 // being obtained from constants.
243 String query = String.format(
244 "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
246 + " (%2$s:subjectCsid = '%3$s' "
247 + " AND %2$s:objectDocumentType = '%4$s') "
249 + " (%2$s:objectCsid = '%3$s' "
250 + " AND %2$s:subjectDocumentType = '%4$s') "
252 + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
253 RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, movementCsid, COLLECTIONOBJECT_DOCTYPE);
255 DocumentModelList relationDocModels = null;
257 relationDocModels = coreSession.query(query);
258 } catch (DocumentException e) {
259 getLogger().error(String.format("Error running this NXQL query: %s", query), e);
262 if (relationDocModels == null || relationDocModels.isEmpty()) {
266 // Iterate through the list of Relation records found and build
267 // a list of CollectionObject CSIDs, by extracting the relevant CSIDs
268 // from those Relation records.
270 for (DocumentModel relationDocModel : relationDocModels) {
271 csid = getCsidForDesiredDocTypeFromRelation(relationDocModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
272 if (Tools.notBlank(csid)) {
281 // Returns true if this event is for the creation of a new relationship record
283 private static boolean isCreatingNewRelationship(Event event) {
284 boolean result = false;
286 DocumentModel docModel = ((DocumentEventContext)event.getContext()).getSourceDocument();
287 if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED) && documentMatchesType(docModel, RELATION_DOCTYPE)) {
294 // FIXME: A quick first pass, using an only partly query-based technique for
295 // getting the most recent Movement record related to a CollectionObject,
296 // augmented by procedural code.
298 // Could be replaced by a potentially more performant method, based on a query.
300 // E.g. the following is a sample CMIS query for retrieving Movement records
301 // related to a CollectionObject, which might serve as the basis for that query.
303 "SELECT DOC.nuxeo:pathSegment, DOC.dc:title, REL.dc:title,"
304 + "REL.relations_common:objectCsid, REL.relations_common:subjectCsid FROM Movement DOC "
305 + "JOIN Relation REL ON REL.relations_common:objectCsid = DOC.nuxeo:pathSegment "
306 + "WHERE REL.relations_common:subjectCsid = '5b4c617e-53a0-484b-804e' "
307 + "AND DOC.nuxeo:isVersion = false "
308 + "ORDER BY DOC.collectionspace_core:updatedAt DESC";
311 * Returns the most recent Movement record related to a CollectionObject.
313 * This method currently returns the related Movement record with the latest
314 * (i.e. most recent in time) Location Date field value.
316 * @param session a repository session.
317 * @param collectionObjectCsid a CollectionObject identifier (CSID)
318 * @param isAboutToBeRemovedEvent whether the current event involves a
319 * record that is slated for removal (hard deletion)
320 * @param movementCsidToFilter the CSID of a Movement record slated for
321 * deletion, or of a Movement record referenced by a Relation record slated
322 * for deletion. This record should be filtered out, prior to returning the
323 * most recent Movement record.
324 * @throws ClientException
325 * @return the most recent Movement record related to the CollectionObject
326 * identified by the supplied CSID.
327 * @throws DocumentException
329 protected String getMostRecentLocation(Event event,
330 CoreSessionInterface session, String collectionObjectCsid,
331 boolean isAboutToBeRemovedEvent, String eventMovementCsid) throws ClientException {
333 // Assume we can determine the most recent location by creating an indeterminate result
335 String result = INDETERMINATE_LOCATION;
338 // Get active Relation records involving Movement records related to this CollectionObject.
340 String query = String.format(
341 "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
343 + " (%2$s:subjectCsid = '%3$s' "
344 + " AND %2$s:objectDocumentType = '%4$s') "
346 + " (%2$s:objectCsid = '%3$s' "
347 + " AND %2$s:subjectDocumentType = '%4$s') "
349 + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
350 RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, collectionObjectCsid, MOVEMENT_DOCTYPE);
351 getLogger().trace("query=" + query);
353 DocumentModelList relationDocModels;
355 relationDocModels = session.query(query);
356 } catch (DocumentException e) {
357 getLogger().error(String.format("Error running this NXQL query: %s", query), e);
361 if (isCreatingNewRelationship(event) == true) {
362 DocumentModel newRelation = ((DocumentEventContext)event.getContext()).getSourceDocument();
363 relationDocModels.add(newRelation);
367 // Remove redundant document models from the list.
369 relationDocModels = removeRedundantRelations(relationDocModels);
372 // Remove relationships that are with inactive movement records
374 relationDocModels = removeInactiveRelations(session, relationDocModels, isAboutToBeRemovedEvent, eventMovementCsid);
377 // If there are no candidate relationships after we removed the duplicates and inactive ones,
378 // throw an exception.
380 if (relationDocModels == null || relationDocModels.size() == 0) {
385 // If there is only one related movement record, then return it as the most recent
386 // movement record -but only if it's current location element is not empty.
388 if (relationDocModels.size() == 1) {
389 DocumentModel relationDocModel = relationDocModels.get(0);
390 DocumentModel movementDocModel = getMovementDocModelFromRelation(session, relationDocModel);
391 String location = (String) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, CURRENT_LOCATION_ELEMENT_NAME);
393 if (Tools.isBlank(location) == false) {
395 } else { // currentLocation must be set
396 getLogger().error(String.format("Movement record=%s is missing its required location value and so is excluded from the computation of cataloging record=%s's current location.",
397 NuxeoUtils.getCsid(movementDocModel), collectionObjectCsid));
404 // Iterate through the list (>2) of related movement records, to find the related
405 // Movement record with the most recent location date.
407 GregorianCalendar mostRecentLocationDate = EARLIEST_COMPARISON_DATE;
408 GregorianCalendar mostRecentUpdatedDate = EARLIEST_COMPARISON_DATE;
410 for (DocumentModel relationDocModel : relationDocModels) {
411 String relatedMovementCsid;
412 DocumentModel movementDocModel;
414 // The movement record is either the subject or object of the relationship, but not both.
416 relatedMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
417 if (relatedMovementCsid.equals(collectionObjectCsid)) {
418 relatedMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
420 movementDocModel = getCurrentDocModelFromCsid(session, relatedMovementCsid);
421 String location = (String) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, CURRENT_LOCATION_ELEMENT_NAME);
424 // If the current Movement record lacks a location date, it cannot
425 // be established as the most recent Movement record; skip over it.
427 GregorianCalendar locationDate = (GregorianCalendar) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, LOCATION_DATE_PROPERTY);
428 if (locationDate == null) {
429 getLogger().info(String.format("Movement record=%s has no location date and so is excluded from computation of cataloging record=%s current location.",
430 NuxeoUtils.getCsid(movementDocModel), collectionObjectCsid));
434 GregorianCalendar updatedDate = (GregorianCalendar) movementDocModel.getProperty(COLLECTIONSPACE_CORE_SCHEMA, UPDATED_AT_PROPERTY);
435 if (locationDate.after(mostRecentLocationDate)) {
436 mostRecentLocationDate = locationDate;
437 mostRecentUpdatedDate = updatedDate;
439 } else if (locationDate.compareTo(mostRecentLocationDate) == 0) {
440 // If the current Movement record's location date is identical
441 // to that of the (at this time) most recent Movement record, then
442 // instead compare the two records using their update date values
443 if (updatedDate.after(mostRecentUpdatedDate)) {
444 // The most recent location date value doesn't need to be
445 // updated here, as the two records' values are identical
446 mostRecentUpdatedDate = updatedDate;
456 // This method assumes that the relation passed into this method is between a Movement record
457 // and a CollectionObject (cataloging) record.
459 private DocumentModel getMovementDocModelFromRelation(CoreSessionInterface session, DocumentModel relationDocModel) {
460 String movementCsid = null;
462 String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
463 if (subjectDocType.endsWith(MOVEMENT_DOCTYPE)) {
464 movementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
466 movementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
469 return getCurrentDocModelFromCsid(session, movementCsid);
473 // Compares two Relation document models to see if they're either identical or
474 // reciprocal equivalents.
476 private static boolean compareRelationDocModels(DocumentModel r1, DocumentModel r2) {
477 boolean result = false;
479 String r1_subjectDocType = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
480 String r1_objectDocType = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
481 String r1_subjectCsid = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
482 String r1_objectCsid = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
484 String r2_subjectDocType = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
485 String r2_objectDocType = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
486 String r2_subjectCsid = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
487 String r2_objectCsid = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
489 // Check to see if they're identical
490 if (r1_subjectDocType.equalsIgnoreCase(r2_subjectDocType) && r1_objectDocType.equalsIgnoreCase(r2_objectDocType)
491 && r1_subjectCsid.equalsIgnoreCase(r2_subjectCsid) && r1_objectCsid.equalsIgnoreCase(r2_objectCsid)) {
495 // Check to see if they're reciprocal
496 if (r1_subjectDocType.equalsIgnoreCase(r2_objectDocType) && r1_objectDocType.equalsIgnoreCase(r2_subjectDocType)
497 && r1_subjectCsid.equalsIgnoreCase(r2_objectCsid) && r1_objectCsid.equalsIgnoreCase(r2_subjectCsid)) {
505 // Return a Relation document model list with redundant (either identical or reciprocal) relations removed.
507 private static DocumentModelList removeRedundantRelations(DocumentModelList relationDocModelList) {
508 DocumentModelList resultList = null;
510 if (relationDocModelList != null && relationDocModelList.size() > 0) {
511 resultList = new DocumentModelListImpl();
512 for (DocumentModel relationDocModel : relationDocModelList) {
513 if (existsInResultList(resultList, relationDocModel) == false) {
514 resultList.add(relationDocModel);
523 // Return just the list of active relationships with active Movement records. A value of 'true' for the 'isAboutToBeRemovedEvent'
524 // argument indicates that relationships with the 'movementCsid' record should be considered inactive.
526 private DocumentModelList removeInactiveRelations(CoreSessionInterface session,
527 DocumentModelList relationDocModelList,
528 boolean isAboutToBeRemovedEvent,
529 String eventMovementCsid) {
530 DocumentModelList resultList = null;
532 if (relationDocModelList != null && relationDocModelList.size() > 0) {
533 resultList = new DocumentModelListImpl();
534 for (DocumentModel relationDocModel : relationDocModelList) {
535 String movementCsid = getCsidForDesiredDocTypeFromRelation(relationDocModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
536 DocumentModel movementDocModel = getCurrentDocModelFromCsid(session, movementCsid);
537 if (isActiveDocument(movementDocModel, isAboutToBeRemovedEvent, eventMovementCsid) == true) {
538 resultList.add(relationDocModel);
540 getLogger().debug(String.format("Disqualified relationship=%s with Movement record=%s from current location computation.",
541 NuxeoUtils.getCsid(relationDocModel), movementCsid));
550 // Check to see if the Relation (or its equivalent reciprocal) is already in the list.
552 private static boolean existsInResultList(DocumentModelList relationDocModelList, DocumentModel relationDocModel) {
553 boolean result = false;
555 for (DocumentModel target : relationDocModelList) {
556 if (compareRelationDocModels(relationDocModel, target) == true) {
566 * Returns the CSID for a desired document type from a Relation record,
567 * where the relationship involves two specified document types.
569 * @param relationDocModel a document model for a Relation record.
570 * @param desiredDocType a desired document type.
571 * @param relatedDocType a related document type.
572 * @throws ClientException
574 * @return the CSID from the desired document type in the relation. Returns
575 * null if the Relation record does not involve both the desired
576 * and related document types.
578 protected static String getCsidForDesiredDocTypeFromRelation(DocumentModel relationDocModel,
579 String desiredDocType, String relatedDocType) {
581 String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
582 String objectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
584 if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(relatedDocType)) { // Use startsWith() method, because customized tenant type names differ in their suffix.
585 csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
586 } else if (subjectDocType.startsWith(relatedDocType) && objectDocType.startsWith(desiredDocType)) {
587 csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
593 // The following method can be extended by sub-classes to update
594 // different/multiple values; e.g. values for moveable locations ("crates").
596 * Updates a CollectionObject record with selected values from a Movement
599 * @param collectionObjectDocModel a document model for a CollectionObject
601 * @param movementDocModel a document model for a Movement record.
602 * @return a potentially updated document model for the CollectionObject
604 * @throws ClientException
606 protected abstract boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
607 DocumentModel movmentDocModel,
608 String movementRecordsLocation);