1 package org.collectionspace.services.listener;
3 import java.util.GregorianCalendar;
4 import java.util.HashSet;
7 import org.apache.commons.logging.Log;
8 import org.apache.commons.logging.LogFactory;
10 import org.collectionspace.services.client.workflow.WorkflowClient;
11 import org.collectionspace.services.common.api.Tools;
12 import org.collectionspace.services.movement.nuxeo.MovementConstants;
13 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
14 import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper;
15 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
17 import org.nuxeo.ecm.core.api.ClientException;
18 import org.nuxeo.ecm.core.api.DocumentModel;
19 import org.nuxeo.ecm.core.api.DocumentModelList;
20 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
21 import org.nuxeo.ecm.core.event.Event;
22 import org.nuxeo.ecm.core.event.EventContext;
23 import org.nuxeo.ecm.core.event.EventListener;
24 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
26 public abstract class AbstractUpdateObjectLocationValues implements EventListener {
28 // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
29 // am using the latter to follow Ray's pattern for now
30 private final static Log logger = LogFactory.getLog(AbstractUpdateObjectLocationValues.class);
31 // FIXME: Make the following message, or its equivalent, a constant usable by all event listeners
32 private final static String NO_FURTHER_PROCESSING_MESSAGE =
33 "This event listener will not continue processing this event ...";
34 private final static GregorianCalendar EARLIEST_COMPARISON_DATE = new GregorianCalendar(1600, 1, 1);
35 private final static String RELATIONS_COMMON_SCHEMA = "relations_common"; // FIXME: Get from external constant
36 private final static String RELATION_DOCTYPE = "Relation"; // FIXME: Get from external constant
37 private final static String SUBJECT_CSID_PROPERTY = "subjectCsid"; // FIXME: Get from external constant
38 private final static String OBJECT_CSID_PROPERTY = "objectCsid"; // FIXME: Get from external constant
39 private final static String SUBJECT_DOCTYPE_PROPERTY = "subjectDocumentType"; // FIXME: Get from external constant
40 private final static String OBJECT_DOCTYPE_PROPERTY = "objectDocumentType"; // FIXME: Get from external constant
41 protected final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common"; // FIXME: Get from external constant
42 private final static String COLLECTIONOBJECT_DOCTYPE = "CollectionObject"; // FIXME: Get from external constant
43 protected final static String COMPUTED_CURRENT_LOCATION_PROPERTY = "computedCurrentLocation"; // FIXME: Create and then get from external constant
44 protected final static String MOVEMENTS_COMMON_SCHEMA = "movements_common"; // FIXME: Get from external constant
45 private final static String MOVEMENT_DOCTYPE = MovementConstants.NUXEO_DOCTYPE;
46 private final static String LOCATION_DATE_PROPERTY = "locationDate"; // FIXME: Get from external constant
47 protected final static String CURRENT_LOCATION_PROPERTY = "currentLocation"; // FIXME: Get from external constant
48 protected final static String COLLECTIONSPACE_CORE_SCHEMA = "collectionspace_core"; // FIXME: Get from external constant
49 protected final static String CREATED_AT_PROPERTY = "createdAt"; // FIXME: Get from external constant
50 protected final static String UPDATED_AT_PROPERTY = "updatedAt"; // FIXME: Get from external constant
51 private final static String NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
52 "AND ecm:isCheckedInVersion = 0"
53 + "AND ecm:isProxy = 0 ";
54 private final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
55 "AND (ecm:currentLifeCycleState <> 'deleted') "
56 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
58 public enum EventNotificationDocumentType {
59 // Document type about which we've received a notification
65 public void handleEvent(Event event) throws ClientException {
67 boolean isAboutToBeRemovedEvent = false;
68 String movementCsidToFilter = "";
69 String eventType = "";
71 logger.trace("In handleEvent in UpdateObjectLocationOnMove ...");
73 EventContext eventContext = event.getContext();
74 if (eventContext == null) {
78 if (!(eventContext instanceof DocumentEventContext)) {
81 DocumentEventContext docEventContext = (DocumentEventContext) eventContext;
82 DocumentModel docModel = docEventContext.getSourceDocument();
84 eventType = event.getName();
85 if (logger.isTraceEnabled()) {
86 logger.trace("A(n) " + eventType + " event was received by UpdateObjectLocationOnMove ...");
88 if (eventType.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) {
89 isAboutToBeRemovedEvent = true;
92 // If this document event involves a Relation record, does this pertain to
93 // a relationship between a Movement record and a CollectionObject record?
95 // If not, we're not interested in processing this document event
96 // in this event handler, as it will have no bearing on updating a
97 // computed current location for a CollectionObject.
100 // (The rest of the code flow below is then identical to that which
101 // is followed when this document event involves a Movement record.)
102 String movementCsid = "";
103 Enum<EventNotificationDocumentType> notificationDocumentType;
104 if (documentMatchesType(docModel, RELATION_DOCTYPE)) {
105 if (logger.isTraceEnabled()) {
106 logger.trace("An event involving a Relation document was received by UpdateObjectLocationOnMove ...");
108 // Get a Movement CSID from the Relation record.
110 // If we can't get it - if this Relation doesn't involve a
111 // Movement - then we don't have a pertinent relation record
112 // that can be processed by this event listener / handler.)
113 movementCsid = getCsidForDesiredDocTypeFromRelation(docModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
114 if (Tools.isBlank(movementCsid)) {
115 logger.warn("Could not obtain CSID for Movement record from document event.");
116 logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
119 // If this Relation record is about to be (hard) deleted, set aside
120 // the CSID for the Movement record to which it pertains, so it can
121 // be filtered out in all subsequent processing of the pertinent
122 // Cataloging record's computed current location.
123 if (isAboutToBeRemovedEvent) {
124 movementCsidToFilter = movementCsid;
126 notificationDocumentType = EventNotificationDocumentType.RELATION;
127 } else if (documentMatchesType(docModel, MOVEMENT_DOCTYPE)) {
128 // Otherwise, get a Movement CSID directly from the Movement record.
129 if (logger.isTraceEnabled()) {
130 logger.trace("An event involving a Movement document was received by UpdateObjectLocationOnMove ...");
132 // FIXME: exclude update events for Movement records here, if we can
133 // identify that we'll still be properly handling update events
134 // that include a relations list as part of the update payload,
135 // perhaps because that may trigger a separate event notification.
136 movementCsid = NuxeoUtils.getCsid(docModel);
137 if (Tools.isBlank(movementCsid)) {
138 logger.warn("Could not obtain CSID for Movement record from document event.");
139 logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
142 // If this Movement record is about to be (hard) deleted, set aside
143 // its CSID so it can be filtered out in all subsequent processing
144 // of the pertinent Cataloging record's computed current location.
145 if (isAboutToBeRemovedEvent) {
146 movementCsidToFilter = movementCsid;
148 notificationDocumentType = EventNotificationDocumentType.MOVEMENT;
150 if (logger.isTraceEnabled()) {
151 logger.trace("This event did not involve a document relevant to UpdateObjectLocationOnMove ...");
156 // Note: currently, all Document lifecycle transitions on
157 // the relevant doctype(s) are handled by this event handler,
158 // not just transitions between 'soft deleted' and active states.
160 // We are assuming that we'll want to re-compute current locations
161 // for related CollectionObjects on all such transitions, as the
162 // semantics of such transitions are opaque to this event handler,
163 // because arbitrary workflows can be bound to those doctype(s).
165 // If we need to filter out some of those lifecycle transitions,
166 // such as excluding transitions to the 'locked' workflow state; or,
167 // alternately, if we want to restrict this event handler's
168 // scope to handle only transitions into the 'soft deleted' state,
169 // we can add additional checks for doing so at this point in the code.
172 if (logger.isTraceEnabled()) {
173 logger.trace("Movement CSID=" + movementCsid);
174 logger.trace("Notification document type=" + notificationDocumentType.name());
177 // All Nuxeo sessions that get passed around to CollectionSpace code need to be
178 // wrapped inside of a CoreSessionWrapper
179 CoreSessionInterface coreSession = new CoreSessionWrapper(docEventContext.getCoreSession());
180 Set<String> collectionObjectCsids = new HashSet<>();
182 if (notificationDocumentType == EventNotificationDocumentType.RELATION) {
183 String relatedCollectionObjectCsid =
184 getCsidForDesiredDocTypeFromRelation(docModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
185 collectionObjectCsids.add(relatedCollectionObjectCsid);
186 } else if (notificationDocumentType == EventNotificationDocumentType.MOVEMENT) {
187 collectionObjectCsids.addAll(getCollectionObjectCsidsRelatedToMovement(movementCsid, coreSession));
190 if (collectionObjectCsids.isEmpty()) {
191 if (logger.isTraceEnabled()) {
192 logger.trace("Could not obtain any CSIDs of related CollectionObject records.");
193 logger.trace(NO_FURTHER_PROCESSING_MESSAGE);
197 if (logger.isTraceEnabled()) {
198 logger.trace("Found " + collectionObjectCsids.size() + " CSID(s) of related CollectionObject records.");
201 // Iterate through the list of CollectionObject CSIDs found.
202 // For each CollectionObject, obtain its most recent, related Movement,
203 // and update relevant field(s) with values from that Movement record.
204 DocumentModel collectionObjectDocModel;
205 DocumentModel mostRecentMovementDocModel;
206 for (String collectionObjectCsid : collectionObjectCsids) {
207 if (logger.isTraceEnabled()) {
208 logger.trace("CollectionObject CSID=" + collectionObjectCsid);
210 // Verify that the CollectionObject is both retrievable and active.
211 collectionObjectDocModel = getCurrentDocModelFromCsid(coreSession, collectionObjectCsid);
212 if (collectionObjectDocModel == null) {
213 if (logger.isTraceEnabled()) {
214 logger.trace("CollectionObject is not current (i.e. is a non-current version), is a proxy, or is unretrievable.");
218 // Verify that the CollectionObject record is active.
219 if (!isActiveDocument(collectionObjectDocModel)) {
220 if (logger.isTraceEnabled()) {
221 logger.trace("CollectionObject is inactive (i.e. deleted or in an otherwise inactive lifestyle state).");
225 // Get the CollectionObject's most recent, related Movement.
226 mostRecentMovementDocModel = getMostRecentMovement(coreSession, collectionObjectCsid,
227 isAboutToBeRemovedEvent, movementCsidToFilter);
228 if (mostRecentMovementDocModel == null) {
231 // Update the CollectionObject with values from that Movement.
232 collectionObjectDocModel =
233 updateCollectionObjectValuesFromMovement(collectionObjectDocModel, mostRecentMovementDocModel);
234 if (logger.isTraceEnabled()) {
235 String computedCurrentLocationRefName =
236 (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA,
237 COMPUTED_CURRENT_LOCATION_PROPERTY);
238 logger.trace("computedCurrentLocation refName after value update=" + computedCurrentLocationRefName);
240 coreSession.saveDocument(collectionObjectDocModel);
245 * Returns the CSIDs of CollectionObject records that are related to a
248 * @param movementCsid the CSID of a Movement record.
249 * @param coreSession a repository session.
250 * @throws ClientException
251 * @return the CSIDs of the CollectionObject records, if any, which are
252 * related to the Movement record.
254 private Set<String> getCollectionObjectCsidsRelatedToMovement(String movementCsid,
255 CoreSessionInterface coreSession) throws ClientException {
257 Set<String> csids = new HashSet<>();
259 // Via an NXQL query, get a list of active relation records where:
260 // * This movement record's CSID is the subject CSID of the relation,
261 // and its object document type is a CollectionObject doctype;
263 // * This movement record's CSID is the object CSID of the relation,
264 // and its subject document type is a CollectionObject doctype.
266 // Some values below are hard-coded for readability, rather than
267 // being obtained from constants.
268 String query = String.format(
269 "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
271 + " (%2$s:subjectCsid = '%3$s' "
272 + " AND %2$s:objectDocumentType = '%4$s') "
274 + " (%2$s:objectCsid = '%3$s' "
275 + " AND %2$s:subjectDocumentType = '%4$s') "
277 + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
278 RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, movementCsid, COLLECTIONOBJECT_DOCTYPE);
279 DocumentModelList relationDocModels = coreSession.query(query);
280 if (relationDocModels == null || relationDocModels.isEmpty()) {
281 // Encountering a Movement record that is not related to any
282 // CollectionObject is potentially a normal occurrence, so no
283 // error messages are logged here when we stop handling this event.
286 // Iterate through the list of Relation records found and build
287 // a list of CollectionObject CSIDs, by extracting the relevant CSIDs
288 // from those Relation records.
290 for (DocumentModel relationDocModel : relationDocModels) {
291 csid = getCsidForDesiredDocTypeFromRelation(relationDocModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
292 if (Tools.notBlank(csid)) {
299 // FIXME: Generic methods like many of those below might be split off from
300 // this specific event listener/handler, into an event handler utilities
301 // class, base classes, or otherwise.
303 // FIXME: Identify whether the equivalent of the documentMatchesType utility
304 // method is already implemented and substitute a call to the latter if so.
305 // This may well already exist.
307 * Identifies whether a document matches a supplied document type.
309 * @param docModel a document model.
310 * @param docType a document type string.
311 * @return true if the document matches the supplied document type; false if
314 protected static boolean documentMatchesType(DocumentModel docModel, String docType) {
315 if (docModel == null || Tools.isBlank(docType)) {
318 if (docModel.getType().startsWith(docType)) {
326 * Identifies whether a document is an active document; currently, whether
327 * it is not in the 'deleted' workflow state.
330 * @return true if the document is an active document; false if it is not.
332 protected static boolean isActiveDocument(DocumentModel docModel) {
333 if (docModel == null) {
336 boolean isActiveDocument = false;
338 if (!docModel.getCurrentLifeCycleState().contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
339 isActiveDocument = true;
341 } catch (ClientException ce) {
342 logger.warn("Error while identifying whether document is an active document: ", ce);
344 return isActiveDocument;
348 * Returns the current document model for a record identified by a CSID.
350 * Excludes documents which have been versioned (i.e. are a non-current
351 * version of a document), are a proxy for another document, or are
352 * un-retrievable via their CSIDs.
354 * @param session a repository session.
355 * @param collectionObjectCsid a CollectionObject identifier (CSID)
356 * @return a document model for the document identified by the supplied
359 protected static DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String collectionObjectCsid) {
360 DocumentModelList collectionObjectDocModels = null;
362 final String query = "SELECT * FROM "
363 + NuxeoUtils.BASE_DOCUMENT_TYPE
365 + NuxeoUtils.getByNameWhereClause(collectionObjectCsid)
367 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
368 collectionObjectDocModels = session.query(query);
369 } catch (Exception e) {
370 logger.warn("Exception in query to get active document model for CollectionObject: ", e);
372 if (collectionObjectDocModels == null || collectionObjectDocModels.isEmpty()) {
373 logger.warn("Could not get active document models for CollectionObject(s).");
375 } else if (collectionObjectDocModels.size() != 1) {
376 logger.debug("Found more than 1 active document with CSID=" + collectionObjectCsid);
379 return collectionObjectDocModels.get(0);
382 // FIXME: A quick first pass, using an only partly query-based technique for
383 // getting the most recent Movement record related to a CollectionObject,
384 // augmented by procedural code.
386 // Could be replaced by a potentially more performant method, based on a query.
388 // E.g. the following is a sample CMIS query for retrieving Movement records
389 // related to a CollectionObject, which might serve as the basis for that query.
391 "SELECT DOC.nuxeo:pathSegment, DOC.dc:title, REL.dc:title,"
392 + "REL.relations_common:objectCsid, REL.relations_common:subjectCsid FROM Movement DOC "
393 + "JOIN Relation REL ON REL.relations_common:objectCsid = DOC.nuxeo:pathSegment "
394 + "WHERE REL.relations_common:subjectCsid = '5b4c617e-53a0-484b-804e' "
395 + "AND DOC.nuxeo:isVersion = false "
396 + "ORDER BY DOC.collectionspace_core:updatedAt DESC";
399 * Returns the most recent Movement record related to a CollectionObject.
401 * This method currently returns the related Movement record with the latest
402 * (i.e. most recent in time) Location Date field value.
404 * @param session a repository session.
405 * @param collectionObjectCsid a CollectionObject identifier (CSID)
406 * @param isAboutToBeRemovedEvent whether the current event involves a
407 * record that is slated for removal (hard deletion)
408 * @param movementCsidToFilter the CSID of a Movement record slated for
409 * deletion, or of a Movement record referenced by a Relation record slated
410 * for deletion. This record should be filtered out, prior to returning the
411 * most recent Movement record.
412 * @throws ClientException
413 * @return the most recent Movement record related to the CollectionObject
414 * identified by the supplied CSID.
416 protected static DocumentModel getMostRecentMovement(CoreSessionInterface session, String collectionObjectCsid,
417 boolean isAboutToBeRemovedEvent, String aboutToBeRemovedMovementCsidToFilter)
418 throws ClientException {
419 DocumentModel mostRecentMovementDocModel = null;
420 // Get Relation records for Movements related to this CollectionObject.
422 // Some values below are hard-coded for readability, rather than
423 // being obtained from constants.
424 String query = String.format(
425 "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
427 + " (%2$s:subjectCsid = '%3$s' "
428 + " AND %2$s:objectDocumentType = '%4$s') "
430 + " (%2$s:objectCsid = '%3$s' "
431 + " AND %2$s:subjectDocumentType = '%4$s') "
433 + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
434 RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, collectionObjectCsid, MOVEMENT_DOCTYPE);
435 if (logger.isTraceEnabled()) {
436 logger.trace("query=" + query);
438 DocumentModelList relationDocModels = session.query(query);
439 if (relationDocModels == null || relationDocModels.isEmpty()) {
440 logger.warn("Unexpectedly found no relations to Movement records to/from to this CollectionObject record.");
441 return mostRecentMovementDocModel;
443 if (logger.isTraceEnabled()) {
444 logger.trace("Found " + relationDocModels.size() + " relations to Movement records to/from this CollectionObject record.");
447 // Iterate through related movement records, to find the related
448 // Movement record with the most recent location date.
449 GregorianCalendar mostRecentLocationDate = EARLIEST_COMPARISON_DATE;
450 // Note: the following value is used to compare any two records, rather
451 // than to identify the most recent value so far encountered. Thus, it may
452 // occasionally be set backward or forward in time, within the loop below.
453 GregorianCalendar comparisonUpdatedDate = EARLIEST_COMPARISON_DATE;
454 DocumentModel movementDocModel;
455 Set<String> alreadyProcessedMovementCsids = new HashSet<>();
456 String relatedMovementCsid;
457 for (DocumentModel relationDocModel : relationDocModels) {
458 // Due to the 'OR' operator in the query above, related Movement
459 // record CSIDs may reside in either the subject or object CSID fields
460 // of the relation record. Whichever CSID value doesn't match the
461 // CollectionObject's CSID is inferred to be the related Movement record's CSID.
462 relatedMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
463 if (relatedMovementCsid.equals(collectionObjectCsid)) {
464 relatedMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
466 if (Tools.isBlank(relatedMovementCsid)) {
469 // Because of the OR clause used in the query above, there may be
470 // two or more Relation records returned in the query results that
471 // reference the same Movement record, as either the subject
472 // or object of a relation to the same CollectionObject record;
473 // we need to filter out those duplicates.
474 if (alreadyProcessedMovementCsids.contains(relatedMovementCsid)) {
477 alreadyProcessedMovementCsids.add(relatedMovementCsid);
479 if (logger.isTraceEnabled()) {
480 logger.trace("Related movement CSID=" + relatedMovementCsid);
482 // If our event involves a Movement record that is about to be
483 // (hard) deleted, or a Movement record referenced by a Relation
484 // record that is about to be (hard) deleted, filter out that record.
485 if (isAboutToBeRemovedEvent && Tools.notBlank(aboutToBeRemovedMovementCsidToFilter)) {
486 if (relatedMovementCsid.equals(aboutToBeRemovedMovementCsidToFilter)) {
487 if (logger.isTraceEnabled()) {
488 logger.trace("Skipping about-to-be-deleted Movement record or referenced, related Movement record ...");
493 movementDocModel = getCurrentDocModelFromCsid(session, relatedMovementCsid);
494 if (movementDocModel == null) {
495 if (logger.isTraceEnabled()) {
496 logger.trace("Movement is not current (i.e. is a non-current version), is a proxy, or is unretrievable.");
500 // Verify that the Movement record is active.
501 if (!isActiveDocument(movementDocModel)) {
502 if (logger.isTraceEnabled()) {
503 logger.trace("Movement is inactive (i.e. is deleted or in another inactive lifestyle state).");
507 GregorianCalendar locationDate =
508 (GregorianCalendar) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, LOCATION_DATE_PROPERTY);
509 // If the current Movement record lacks a location date, it cannot
510 // be established as the most recent Movement record; skip over it.
511 if (locationDate == null) {
514 GregorianCalendar updatedDate =
515 (GregorianCalendar) movementDocModel.getProperty(COLLECTIONSPACE_CORE_SCHEMA, UPDATED_AT_PROPERTY);
516 if (locationDate.after(mostRecentLocationDate)) {
517 mostRecentLocationDate = locationDate;
518 if (updatedDate != null) {
519 comparisonUpdatedDate = updatedDate;
521 mostRecentMovementDocModel = movementDocModel;
522 // If the current Movement record's location date is identical
523 // to that of the (at this time) most recent Movement record, then
524 // instead compare the two records using their update date values
525 } else if (locationDate.compareTo(mostRecentLocationDate) == 0) {
526 if (updatedDate != null && updatedDate.after(comparisonUpdatedDate)) {
527 // The most recent location date value doesn't need to be
528 // updated here, as the two records' values are identical
529 comparisonUpdatedDate = updatedDate;
530 mostRecentMovementDocModel = movementDocModel;
534 return mostRecentMovementDocModel;
538 * Returns the CSID for a desired document type from a Relation record,
539 * where the relationship involves two specified, different document types.
541 * @param relationDocModel a document model for a Relation record.
542 * @param desiredDocType a desired document type.
543 * @param relatedDocType a related document type.
544 * @throws ClientException
545 * @return the CSID from the desired document type in the relation. Returns
546 * an empty string if the Relation record does not involve both the desired
547 * and related document types, or if the desired document type is at both
548 * ends of the relation.
550 protected static String getCsidForDesiredDocTypeFromRelation(DocumentModel relationDocModel,
551 String desiredDocType, String relatedDocType) throws ClientException {
553 String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
554 String objectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
555 if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(desiredDocType)) {
558 if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(relatedDocType)) {
559 csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
560 } else if (subjectDocType.startsWith(relatedDocType) && objectDocType.startsWith(desiredDocType)) {
561 csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
566 // The following method can be extended by sub-classes to update
567 // different/multiple values; e.g. values for moveable locations ("crates").
569 * Updates a CollectionObject record with selected values from a Movement
572 * @param collectionObjectDocModel a document model for a CollectionObject
574 * @param movementDocModel a document model for a Movement record.
575 * @return a potentially updated document model for the CollectionObject
577 * @throws ClientException
579 protected abstract DocumentModel updateCollectionObjectValuesFromMovement(DocumentModel collectionObjectDocModel,
580 DocumentModel movementDocModel)
581 throws ClientException;