]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-5727: Refactored into abstract and implementation methods for easier extensibi...
authorAron Roberts <aron@socrates.berkeley.edu>
Thu, 13 Dec 2012 01:40:38 +0000 (17:40 -0800)
committerAron Roberts <aron@socrates.berkeley.edu>
Thu, 13 Dec 2012 01:40:38 +0000 (17:40 -0800)
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/AbstractUpdateObjectLocationValues.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java

diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/AbstractUpdateObjectLocationValues.java b/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/AbstractUpdateObjectLocationValues.java
new file mode 100644 (file)
index 0000000..1df44eb
--- /dev/null
@@ -0,0 +1,422 @@
+package org.collectionspace.services.listener;
+
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.collectionspace.services.client.workflow.WorkflowClient;
+import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.movement.nuxeo.MovementConstants;
+import org.collectionspace.services.nuxeo.util.NuxeoUtils;
+import org.nuxeo.ecm.core.api.ClientException;
+import org.nuxeo.ecm.core.api.CoreSession;
+import org.nuxeo.ecm.core.api.DocumentModel;
+import org.nuxeo.ecm.core.api.DocumentModelList;
+import org.nuxeo.ecm.core.event.Event;
+import org.nuxeo.ecm.core.event.EventContext;
+import org.nuxeo.ecm.core.event.EventListener;
+import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
+
+public abstract class AbstractUpdateObjectLocationValues implements EventListener {
+
+    // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
+    // am using the latter to follow Ray's pattern for now
+    private final static Log logger = LogFactory.getLog(AbstractUpdateObjectLocationValues.class);
+    // FIXME: Make the following message, or its equivalent, a constant usable by all event listeners
+    private final static String NO_FURTHER_PROCESSING_MESSAGE =
+            "This event listener will not continue processing this event ...";
+    private final static GregorianCalendar EARLIEST_COMPARISON_DATE = new GregorianCalendar(1600, 1, 1);
+    private final static String RELATIONS_COMMON_SCHEMA = "relations_common"; // FIXME: Get from external constant
+    private final static String RELATION_DOCTYPE = "Relation"; // FIXME: Get from external constant
+    private final static String SUBJECT_CSID_PROPERTY = "subjectCsid"; // FIXME: Get from external constant
+    private final static String OBJECT_CSID_PROPERTY = "objectCsid"; // FIXME: Get from external constant
+    private final static String SUBJECT_DOCTYPE_PROPERTY = "subjectDocumentType"; // FIXME: Get from external constant
+    private final static String OBJECT_DOCTYPE_PROPERTY = "objectDocumentType"; // FIXME: Get from external constant
+    protected final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common"; // FIXME: Get from external constant
+    private final static String COLLECTIONOBJECT_DOCTYPE = "CollectionObject"; // FIXME: Get from external constant
+    protected final static String COMPUTED_CURRENT_LOCATION_PROPERTY = "computedCurrentLocation"; // FIXME: Create and then get from external constant
+    private final static String MOVEMENTS_COMMON_SCHEMA = "movements_common"; // FIXME: Get from external constant
+    private final static String MOVEMENT_DOCTYPE = MovementConstants.NUXEO_DOCTYPE;
+    private final static String LOCATION_DATE_PROPERTY = "locationDate"; // FIXME: Get from external constant
+    private final static String CURRENT_LOCATION_PROPERTY = "currentLocation"; // FIXME: Get from external constant
+    private final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
+            "AND (ecm:currentLifeCycleState <> 'deleted') "
+            + "AND ecm:isProxy = 0 "
+            + "AND ecm:isCheckedInVersion = 0";
+
+    @Override
+    public void handleEvent(Event event) throws ClientException {
+
+        logger.trace("In handleEvent in UpdateObjectLocationOnMove ...");
+
+        EventContext eventContext = event.getContext();
+        if (eventContext == null) {
+            return;
+        }
+
+        if (!(eventContext instanceof DocumentEventContext)) {
+            return;
+        }
+        DocumentEventContext docEventContext = (DocumentEventContext) eventContext;
+        DocumentModel docModel = docEventContext.getSourceDocument();
+
+        // If this document event involves a Relation record, does this pertain to
+        // a relationship between a Movement record and a CollectionObject record?
+        //
+        // If not, we're not interested in processing this document event
+        // in this event handler, as it will have no bearing on updating a
+        // computed current location for a CollectionObject.
+
+        //
+        // (The rest of the code flow below is then identical to that which
+        // is followed when this document event involves a Movement record.)
+        String movementCsid = "";
+        if (documentMatchesType(docModel, RELATION_DOCTYPE)) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("An event involving a Relation document was received by UpdateObjectLocationOnMove ...");
+            }
+            // Get a Movement CSID from the Relation record. (If we can't
+            // get it, then we don't have a pertinent relation record.)
+            movementCsid = getCsidForDesiredDocType(docModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
+            if (Tools.isBlank(movementCsid)) {
+                logger.warn("Could not obtain CSID for Movement record from document event.");
+                logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
+                return;
+            }
+        } else if (documentMatchesType(docModel, MOVEMENT_DOCTYPE)) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("An event involving a Movement document was received by UpdateObjectLocationOnMove ...");
+            }
+            // Otherwise, get a Movement CSID directly from the Movement record.
+            movementCsid = NuxeoUtils.getCsid(docModel);
+            if (Tools.isBlank(movementCsid)) {
+                logger.warn("Could not obtain CSID for Movement record from document event.");
+                logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
+                return;
+            }
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("This event did not involve a document relevant to UpdateObjectLocationOnMove ...");
+            }
+            return;
+        }
+
+        // Note: currently, all Document lifecycle transitions on
+        // the relevant doctype(s) are handled by this event handler,
+        // not just transitions between 'soft deleted' and active states.
+        //
+        // We are assuming that we'll want to re-compute current locations
+        // for related CollectionObjects on all such transitions, as the
+        // semantics of such transitions are opaque to this event handler,
+        // because arbitrary workflows can be bound to those doctype(s).
+        //
+        // If we need to filter out some of those lifecycle transitions,
+        // such as excluding transitions to the 'locked' workflow state; or,
+        // alternately, if we want to restrict this event handler's
+        // scope to handle only transitions into the 'soft deleted' state,
+        // we can add additional checks for doing so at this point in the code.
+
+        if (logger.isTraceEnabled()) {
+            logger.trace("Movement CSID=" + movementCsid);
+        }
+
+        // Find CollectionObject records that are related to this Movement record:
+        //
+        // Via an NXQL query, get a list of active relation records where:
+        // * This movement record's CSID is the subject CSID of the relation,
+        //   and its object document type is a CollectionObject doctype;
+        // or
+        // * This movement record's CSID is the object CSID of the relation,
+        //   and its subject document type is a CollectionObject doctype.
+        CoreSession coreSession = docEventContext.getCoreSession();
+        // Some values below are hard-coded for readability, rather than
+        // being obtained from constants.
+        String query = String.format(
+                "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
+                + "("
+                + "  (%2$s:subjectCsid = '%3$s' "
+                + "  AND %2$s:objectDocumentType = '%4$s') "
+                + " OR "
+                + "  (%2$s:objectCsid = '%3$s' "
+                + "  AND %2$s:subjectDocumentType = '%4$s') "
+                + ")"
+                + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
+                RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, movementCsid, COLLECTIONOBJECT_DOCTYPE);
+        DocumentModelList relationDocModels = coreSession.query(query);
+        if (relationDocModels == null || relationDocModels.isEmpty()) {
+            // Encountering a Movement record that is not related to any
+            // CollectionObject is potentially a normal occurrence, so no
+            // error messages are logged here when we stop handling this event.
+            return;
+        }
+
+        // Iterate through the list of Relation records found and build
+        // a list of CollectionObject CSIDs, by extracting the relevant CSIDs
+        // from those Relation records.
+
+        // FIXME: The following code might be refactored into a generic 'get
+        // values of a single property from a list of document models' method,
+        // if this doesn't already exist.
+        String csid = "";
+        Set<String> collectionObjectCsids = new HashSet<String>(); // Prevents/removes duplicates on add
+        for (DocumentModel relationDocModel : relationDocModels) {
+            csid = getCsidForDesiredDocType(relationDocModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
+            if (Tools.notBlank(csid)) {
+                collectionObjectCsids.add(csid);
+            }
+        }
+        if (collectionObjectCsids == null || collectionObjectCsids.isEmpty()) {
+            logger.warn("Could not obtain any CSIDs of related CollectionObject records.");
+            logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
+            return;
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Found " + collectionObjectCsids.size() + " CSIDs of related CollectionObject records.");
+            }
+        }
+
+        // Iterate through the list of CollectionObject CSIDs found
+        // and update their location value(s).
+        for (String collectionObjectCsid : collectionObjectCsids) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("CollectionObject CSID=" + collectionObjectCsid);
+            }
+            updateAllLocationValues(coreSession, collectionObjectCsid);
+        }
+    }
+
+    // FIXME: Generic methods like many of those below might be split off,
+    // into an event utilities class, base classes, or otherwise. - ADR 2012-12-05
+    //
+    // FIXME: Identify whether the equivalent of the documentMatchesType utility
+    // method is already implemented and substitute a call to the latter if so.
+    // This may well already exist.
+    /**
+     * Identifies whether a document matches a supplied document type.
+     *
+     * @param docModel a document model.
+     * @param docType a document type string.
+     * @return true if the document matches the supplied document type; false if
+     * it does not.
+     */
+    protected static boolean documentMatchesType(DocumentModel docModel, String docType) {
+        if (docModel == null || Tools.isBlank(docType)) {
+            return false;
+        }
+        if (docModel.getType().startsWith(docType)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Identifies whether a document is an active document; that is, if it is
+     * not a versioned record; not a proxy (symbolic link to an actual record);
+     * and not in the 'deleted' workflow state.
+     *
+     * (A note relating the latter: Nuxeo appears to send 'documentModified'
+     * events even on workflow transitions, such when records are 'soft deleted'
+     * by being transitioned to the 'deleted' workflow state.)
+     *
+     * @param docModel
+     * @return true if the document is an active document; false if it is not.
+     */
+    protected static boolean isActiveDocument(DocumentModel docModel) {
+        if (docModel == null) {
+            return false;
+        }
+        boolean isActiveDocument = false;
+        try {
+            if (!docModel.isVersion()
+                    && !docModel.isProxy()
+                    && !docModel.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
+                isActiveDocument = true;
+            }
+        } catch (ClientException ce) {
+            logger.warn("Error while identifying whether document is an active document: ", ce);
+        }
+        return isActiveDocument;
+    }
+
+    /**
+     * Returns a document model for a record identified by a CSID.
+     *
+     * @param session a repository session.
+     * @param collectionObjectCsid a CollectionObject identifier (CSID)
+     * @return a document model for the record identified by the supplied CSID.
+     */
+    protected static DocumentModel getDocModelFromCsid(CoreSession session, String collectionObjectCsid) {
+        DocumentModelList collectionObjectDocModels = null;
+        try {
+            final String query = "SELECT * FROM "
+                    + NuxeoUtils.BASE_DOCUMENT_TYPE
+                    + " WHERE "
+                    + NuxeoUtils.getByNameWhereClause(collectionObjectCsid);
+            collectionObjectDocModels = session.query(query);
+        } catch (Exception e) {
+            logger.warn("Exception in query to get document model for CollectionObject: ", e);
+        }
+        if (collectionObjectDocModels == null || collectionObjectDocModels.isEmpty()) {
+            logger.warn("Could not get document models for CollectionObject(s).");
+            return null;
+        } else if (collectionObjectDocModels.size() != 1) {
+            logger.debug("Found more than 1 document with CSID=" + collectionObjectCsid);
+            return null;
+        }
+        return collectionObjectDocModels.get(0);
+    }
+
+    // FIXME: A quick first pass, using an only partly query-based technique for
+    // getting the current location, augmented by procedural code.
+    //
+    // Should be replaced by a more performant method, based entirely, or nearly so,
+    // on a query.
+    //
+    // E.g. the following is a sample CMIS query for retrieving Movement records
+    // related to a CollectionObject, which might serve as the basis for that query.
+    /*
+     "SELECT DOC.nuxeo:pathSegment, DOC.dc:title, REL.dc:title,"
+     + "REL.relations_common:objectCsid, REL.relations_common:subjectCsid FROM Movement DOC "
+     + "JOIN Relation REL ON REL.relations_common:objectCsid = DOC.nuxeo:pathSegment "
+     + "WHERE REL.relations_common:subjectCsid = '5b4c617e-53a0-484b-804e' "
+     + "AND DOC.nuxeo:isVersion = false "
+     + "ORDER BY DOC.collectionspace_core:updatedAt DESC";
+     */
+    /**
+     * Returns the computed current location for a CollectionObject.
+     *
+     * @param session a repository session.
+     * @param collectionObjectCsid a CollectionObject identifier (CSID)
+     * @throws ClientException
+     * @return the computed current location for the CollectionObject identified
+     * by the supplied CSID.
+     */
+    protected static String computeCurrentLocation(CoreSession session, String collectionObjectCsid)
+            throws ClientException {
+        String computedCurrentLocation = "";
+        // Get Relation records for Movements related to this CollectionObject.
+        //
+        // Some values below are hard-coded for readability, rather than
+        // being obtained from constants.
+        String query = String.format(
+                "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
+                + "("
+                + "  (%2$s:subjectCsid = '%3$s' "
+                + "  AND %2$s:objectDocumentType = '%4$s') "
+                + " OR "
+                + "  (%2$s:objectCsid = '%3$s' "
+                + "  AND %2$s:subjectDocumentType = '%4$s') "
+                + ")"
+                + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
+                RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, collectionObjectCsid, MOVEMENT_DOCTYPE);
+        if (logger.isTraceEnabled()) {
+            logger.trace("query=" + query);
+        }
+        DocumentModelList relationDocModels = session.query(query);
+        if (relationDocModels == null || relationDocModels.isEmpty()) {
+            logger.warn("Unexpectedly found no relations to Movement records to/from to this CollectionObject record.");
+            return computedCurrentLocation;
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("Found " + relationDocModels.size() + " relations to Movement records to/from this CollectionObject record.");
+            }
+        }
+        // Iterate through related movement records, to get the CollectionObject's
+        // computed current location from the related Movement record with the
+        // most recent location date.
+        GregorianCalendar mostRecentLocationDate = EARLIEST_COMPARISON_DATE;
+        DocumentModel movementDocModel = null;
+        Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
+        String relMovementCsid = "";
+        String location = "";
+        for (DocumentModel relationDocModel : relationDocModels) {
+            // Due to the 'OR' operator in the query above, related Movement
+            // record CSIDs may reside in either the subject or object CSID fields
+            // of the relation record. Whichever CSID value doesn't match the
+            // CollectionObject's CSID is inferred to be the related Movement record's CSID.
+            relMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
+            if (relMovementCsid.equals(collectionObjectCsid)) {
+                relMovementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+            }
+            if (Tools.isBlank(relMovementCsid)) {
+                continue;
+            }
+            // Because of the OR clause used in the query above, there may be
+            // two or more Relation records returned in the query results that
+            // reference the same Movement record, as either the subject
+            // or object of a relation to the same CollectionObject record;
+            // we need to filter out those duplicates.
+            if (alreadyProcessedMovementCsids.contains(relMovementCsid)) {
+                continue;
+            } else {
+                alreadyProcessedMovementCsids.add(relMovementCsid);
+            }
+            if (logger.isTraceEnabled()) {
+                logger.trace("Movement CSID=" + relMovementCsid);
+            }
+            movementDocModel = getDocModelFromCsid(session, relMovementCsid);
+            if (movementDocModel == null) {
+                continue;
+            }
+
+            // Verify that the Movement record is active. This will also exclude
+            // versioned Movement records from the computation of the current
+            // location, for tenants that are versioning such records.
+            if (!isActiveDocument(movementDocModel)) {
+                if (logger.isTraceEnabled()) {
+                    logger.trace("Skipping this inactive Movement record ...");
+                }
+                continue;
+            }
+            GregorianCalendar locationDate =
+                    (GregorianCalendar) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, LOCATION_DATE_PROPERTY);
+            if (locationDate == null) {
+                continue;
+            }
+            if (locationDate.after(mostRecentLocationDate)) {
+                mostRecentLocationDate = locationDate;
+                location = (String) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, CURRENT_LOCATION_PROPERTY);
+                if (Tools.notBlank(location)) {
+                    computedCurrentLocation = location;
+                }
+            }
+        }
+        return computedCurrentLocation;
+    }
+
+    /**
+     * Returns the CSID for a desired document type from a Relation record,
+     * where the relationship involves two specified, different document types.
+     *
+     * @param relationDocModel a document model for a Relation record.
+     * @param desiredDocType a desired document type.
+     * @param relatedDocType a related document type.
+     * @throws ClientException
+     * @return the CSID from the desired document type in the relation. Returns
+     * an empty string if the Relation record does not involve both the desired
+     * and related document types, or if the desired document type is at both
+     * ends of the relation.
+     */
+    protected static String getCsidForDesiredDocType(DocumentModel relationDocModel,
+            String desiredDocType, String relatedDocType) throws ClientException {
+        String csid = "";
+        String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
+        String objectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
+        if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(desiredDocType)) {
+            return csid;
+        }
+        if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(relatedDocType)) {
+            csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
+        } else if (subjectDocType.startsWith(relatedDocType) && objectDocType.startsWith(desiredDocType)) {
+            csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+        }
+        return csid;
+    }
+
+    // Can be extended by sub-classes to update different/multiple values;
+    // e.g. values for moveable locations ("crates").
+    protected abstract void updateAllLocationValues(CoreSession coreSession, String collectionObjectCsid)
+            throws ClientException;
+}
\ No newline at end of file
index 6b8ca9a789a307bcbbd9f3c87838a2a879b48a13..ba33300893617f76bad25cbac82a388cc7df2d64 100644 (file)
 package org.collectionspace.services.listener;
 
-import java.util.ArrayList;
-import java.util.GregorianCalendar;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.collectionspace.services.client.workflow.WorkflowClient;
 import org.collectionspace.services.common.api.RefNameUtils;
 import org.collectionspace.services.common.api.Tools;
-import org.collectionspace.services.movement.nuxeo.MovementConstants;
-import org.collectionspace.services.nuxeo.util.NuxeoUtils;
 import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.CoreSession;
 import org.nuxeo.ecm.core.api.DocumentModel;
-import org.nuxeo.ecm.core.api.DocumentModelList;
-import org.nuxeo.ecm.core.event.Event;
-import org.nuxeo.ecm.core.event.EventContext;
-import org.nuxeo.ecm.core.event.EventListener;
-import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
 
-public class UpdateObjectLocationOnMove implements EventListener {
+public class UpdateObjectLocationOnMove extends AbstractUpdateObjectLocationValues {
 
     // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
     // am using the latter to follow Ray's pattern for now
     private final Log logger = LogFactory.getLog(UpdateObjectLocationOnMove.class);
-    // FIXME: Make the following message, or its equivalent, a constant usable by all event listeners
-    private final String NO_FURTHER_PROCESSING_MESSAGE =
-            "This event listener will not continue processing this event ...";
-    private final List<String> relevantDocTypesList = new ArrayList<String>();
-    GregorianCalendar EARLIEST_COMPARISON_DATE = new GregorianCalendar(1600, 1, 1);
-    private final static String RELATIONS_COMMON_SCHEMA = "relations_common"; // FIXME: Get from external constant
-    private final static String RELATION_DOCTYPE = "Relation"; // FIXME: Get from external constant
-    private final static String SUBJECT_CSID_PROPERTY = "subjectCsid"; // FIXME: Get from external constant
-    private final static String OBJECT_CSID_PROPERTY = "objectCsid"; // FIXME: Get from external constant
-    private final static String SUBJECT_DOCTYPE_PROPERTY = "subjectDocumentType"; // FIXME: Get from external constant
-    private final static String OBJECT_DOCTYPE_PROPERTY = "objectDocumentType"; // FIXME: Get from external constant
-    private final static String COLLECTIONOBJECTS_COMMON_SCHEMA = "collectionobjects_common"; // FIXME: Get from external constant
-    private final static String COLLECTIONOBJECT_DOCTYPE = "CollectionObject"; // FIXME: Get from external constant
-    private final static String COMPUTED_CURRENT_LOCATION_PROPERTY = "computedCurrentLocation"; // FIXME: Create and then get from external constant
-    private final static String MOVEMENTS_COMMON_SCHEMA = "movements_common"; // FIXME: Get from external constant
-    private final static String MOVEMENT_DOCTYPE = MovementConstants.NUXEO_DOCTYPE;
-    private final static String LOCATION_DATE_PROPERTY = "locationDate"; // FIXME: Get from external constant
-    private final static String CURRENT_LOCATION_PROPERTY = "currentLocation"; // FIXME: Get from external constant
-    private final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
-            "AND (ecm:currentLifeCycleState <> 'deleted') "
-            + "AND ecm:isProxy = 0 "
-            + "AND ecm:isCheckedInVersion = 0";
-
+    
     @Override
-    public void handleEvent(Event event) throws ClientException {
-
-        logger.trace("In handleEvent in UpdateObjectLocationOnMove ...");
-
-        EventContext eventContext = event.getContext();
-        if (eventContext == null) {
-            return;
-        }
+    protected void updateAllLocationValues(CoreSession coreSession, String collectionObjectCsid)
+            throws ClientException {
+        updateCurrentLocationValue(coreSession, collectionObjectCsid);
+    }
 
-        if (!(eventContext instanceof DocumentEventContext)) {
+    private void updateCurrentLocationValue(CoreSession coreSession, String collectionObjectCsid)
+            throws ClientException {
+        DocumentModel collectionObjectDocModel;
+        String computedCurrentLocationRefName;
+        collectionObjectDocModel = getDocModelFromCsid(coreSession, collectionObjectCsid);
+        if (collectionObjectDocModel == null) {
             return;
         }
-        DocumentEventContext docEventContext = (DocumentEventContext) eventContext;
-        DocumentModel docModel = docEventContext.getSourceDocument();
-
-        // If this document event involves a Relation record, does this pertain to
-        // a relationship between a Movement record and a CollectionObject record?
-        //
-        // If not, we're not interested in processing this document event
-        // in this event handler, as it will have no bearing on updating a
-        // computed current location for a CollectionObject.
-
-        //
-        // (The rest of the code flow below is then identical to that which
-        // is followed when this document event involves a Movement record.)
-        String movementCsid = "";
-        if (documentMatchesType(docModel, RELATION_DOCTYPE)) {
-            if (logger.isTraceEnabled()) {
-                logger.trace("An event involving a Relation document was received by UpdateObjectLocationOnMove ...");
-            }
-            // Get a Movement CSID from the Relation record. (If we can't
-            // get it, then we don't have a pertinent relation record.)
-            movementCsid = getCsidForDesiredDocType(docModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
-            if (Tools.isBlank(movementCsid)) {
-                logger.warn("Could not obtain CSID for Movement record from document event.");
-                logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
-                return;
-            }
-        } else if (documentMatchesType(docModel, MOVEMENT_DOCTYPE)) {
-            if (logger.isTraceEnabled()) {
-                logger.trace("An event involving a Movement document was received by UpdateObjectLocationOnMove ...");
-            }
-            // Otherwise, get a Movement CSID directly from the Movement record.
-            movementCsid = NuxeoUtils.getCsid(docModel);
-            if (Tools.isBlank(movementCsid)) {
-                logger.warn("Could not obtain CSID for Movement record from document event.");
-                logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
-                return;
-            }
-        } else {
-            if (logger.isTraceEnabled()) {
-                logger.trace("This event did not involve a document relevant to UpdateObjectLocationOnMove ...");
-            }
+        // Verify that the CollectionObject record is active.
+        if (!isActiveDocument(collectionObjectDocModel)) {
             return;
         }
-
-        // Note: currently, all Document lifecycle transitions on
-        // the relevant doctype(s) are handled by this event handler,
-        // not just transitions between 'soft deleted' and active states.
-        //
-        // We are assuming that we'll want to re-compute current locations
-        // for related CollectionObjects on all such transitions, as the
-        // semantics of such transitions are opaque to this event handler,
-        // because arbitrary workflows can be bound to those doctype(s).
-        //
-        // If we need to filter out some of those lifecycle transitions,
-        // such as excluding transitions to the 'locked' workflow state; or,
-        // alternately, if we want to restrict this event handler's
-        // scope to handle only transitions into the 'soft deleted' state,
-        // we can add additional checks for doing so at this point in the code.
-
+        // Obtain the computed current location of that CollectionObject.
+        computedCurrentLocationRefName = computeCurrentLocation(coreSession, collectionObjectCsid);
         if (logger.isTraceEnabled()) {
-            logger.trace("Movement CSID=" + movementCsid);
-        }
-
-        // Find CollectionObject records that are related to this Movement record:
-        //
-        // Via an NXQL query, get a list of active relation records where:
-        // * This movement record's CSID is the subject CSID of the relation,
-        //   and its object document type is a CollectionObject doctype;
-        // or
-        // * This movement record's CSID is the object CSID of the relation,
-        //   and its subject document type is a CollectionObject doctype.
-        CoreSession coreSession = docEventContext.getCoreSession();
-        // Some values below are hard-coded for readability, rather than
-        // being obtained from constants.
-        String query = String.format(
-                "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
-                + "("
-                + "  (%2$s:subjectCsid = '%3$s' "
-                + "  AND %2$s:objectDocumentType = '%4$s') "
-                + " OR "
-                + "  (%2$s:objectCsid = '%3$s' "
-                + "  AND %2$s:subjectDocumentType = '%4$s') "
-                + ")"
-                + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
-                RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, movementCsid, COLLECTIONOBJECT_DOCTYPE);
-        DocumentModelList relationDocModels = coreSession.query(query);
-        if (relationDocModels == null || relationDocModels.isEmpty()) {
-            // Encountering a Movement record that is not related to any
-            // CollectionObject is potentially a normal occurrence, so no
-            // error messages are logged here when we stop handling this event.
-            return;
-        }
-
-        // Iterate through the list of Relation records found and build
-        // a list of CollectionObject CSIDs, by extracting the relevant CSIDs
-        // from those Relation records.
-
-        // FIXME: The following code might be refactored into a generic 'get
-        // values of a single property from a list of document models' method,
-        // if this doesn't already exist.
-        String csid = "";
-        Set<String> collectionObjectCsids = new HashSet<String>(); // Prevents/removes duplicates on add
-        for (DocumentModel relationDocModel : relationDocModels) {
-            csid = getCsidForDesiredDocType(relationDocModel, COLLECTIONOBJECT_DOCTYPE, MOVEMENT_DOCTYPE);
-            if (Tools.notBlank(csid)) {
-                collectionObjectCsids.add(csid);
-            }
-        }
-        if (collectionObjectCsids == null || collectionObjectCsids.isEmpty()) {
-            logger.warn("Could not obtain any CSIDs of related CollectionObject records.");
-            logger.warn(NO_FURTHER_PROCESSING_MESSAGE);
+            logger.trace("computedCurrentLocation refName=" + computedCurrentLocationRefName);
+        }
+
+        // Check that the value returned, which is expected to be a
+        // reference (refName) to a storage location authority term,
+        // is, at a minimum:
+        // * Non-null and non-blank. (We need to verify this assumption; can a
+        //   CollectionObject's computed current location value ever meaningfully
+        //   be 'un-set' by returning it to a null value?)
+        // * Capable of being successfully parsed by an authority item parser;
+        //   that is, returning a non-null parse result.
+        if ((Tools.isBlank(computedCurrentLocationRefName)
+                || (RefNameUtils.parseAuthorityTermInfo(computedCurrentLocationRefName) == null))) {
+            logger.warn("Could not parse computed current location refName '" + computedCurrentLocationRefName + "'");
             return;
         } else {
             if (logger.isTraceEnabled()) {
-                logger.trace("Found " + collectionObjectCsids.size() + " CSIDs of related CollectionObject records.");
+                logger.trace("computed current location refName passes basic validation tests.");
             }
         }
 
-        // Iterate through the list of CollectionObject CSIDs found.
-        DocumentModel collectionObjectDocModel = null;
-        String computedCurrentLocationRefName = "";
-        Map<DocumentModel, String> docModelsToUpdate = new HashMap<DocumentModel, String>();
-        for (String collectionObjectCsid : collectionObjectCsids) {
-
-            if (logger.isTraceEnabled()) {
-                logger.trace("CollectionObject CSID=" + collectionObjectCsids);
-            }
-            collectionObjectDocModel = getDocModelFromCsid(coreSession, collectionObjectCsid);
-            if (collectionObjectDocModel == null) {
-                continue;
-            }
-            // Verify that the CollectionObject record is active.
-            if (!isActiveDocument(collectionObjectDocModel)) {
-                continue;
-            }
-            // Obtain the computed current location of that CollectionObject.
-            computedCurrentLocationRefName = computeCurrentLocation(coreSession, collectionObjectCsid);
+        // If the value returned from the function passes validation,
+        // compare it to the value in the computedCurrentLocation
+        // field of that CollectionObject.
+        String existingComputedCurrentLocationRefName =
+                (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY);
+        if (logger.isTraceEnabled()) {
+            logger.trace("Existing computedCurrentLocation refName=" + existingComputedCurrentLocationRefName);
+        }
+        // If the CollectionObject lacks a computed current location value,
+        // or if the new computed value differs from its existing value ...
+        if (Tools.isBlank(existingComputedCurrentLocationRefName)
+                || (!computedCurrentLocationRefName.equals(existingComputedCurrentLocationRefName))) {
             if (logger.isTraceEnabled()) {
-                logger.trace("computedCurrentLocation refName=" + computedCurrentLocationRefName);
-            }
-
-            // Check that the value returned, which is expected to be a
-            // reference (refName) to a storage location authority term,
-            // is, at a minimum:
-            // * Non-null and non-blank. (We need to verify this assumption; can a
-            //   CollectionObject's computed current location meaningfully be 'un-set'?)
-            // * Capable of being successfully parsed by an authority item parser;
-            //   that is, returning a non-null parse result.
-            if ((Tools.isBlank(computedCurrentLocationRefName)
-                    || (RefNameUtils.parseAuthorityTermInfo(computedCurrentLocationRefName) == null))) {
-                logger.warn("Could not parse computed current location refName '" + computedCurrentLocationRefName + "'");
-                continue;
-            } else {
-                if (logger.isTraceEnabled()) {
-                    logger.trace("refName passes basic validation tests.");
-                }
-
-                // If the value returned from the function passes validation,
-                // compare it to the value in the computedCurrentLocation
-                // field of that CollectionObject.
-                String existingComputedCurrentLocationRefName =
-                        (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY);
-                if (logger.isTraceEnabled()) {
-                    logger.trace("Existing computedCurrentLocation refName=" + existingComputedCurrentLocationRefName);
-                }
-                // If the CollectionObject lacks a computed current location value,
-                // or if the new computed value differs from its existing value ...
-                if (Tools.isBlank(existingComputedCurrentLocationRefName)
-                        || (!computedCurrentLocationRefName.equals(existingComputedCurrentLocationRefName))) {
-                    if (logger.isTraceEnabled()) {
-                        logger.trace("computedCurrentLocation refName requires updating.");
-                    }
-                    // ... set aside this CollectionObject's docModel and its new
-                    // computed current location value for subsequent updating
-                    docModelsToUpdate.put(collectionObjectDocModel, computedCurrentLocationRefName);
-                } else {
-                    if (logger.isTraceEnabled()) {
-                        logger.trace("computedCurrentLocation refName does NOT require updating.");
-                    }
-                }
+                logger.trace("computedCurrentLocation refName requires updating.");
             }
-        }
-
-        // For each CollectionObject docModel that has been set aside for updating,
-        // update the value of its computedCurrentLocation field with its new,
-        // computed current location.
-        int collectionObjectsUpdated = 0;
-        for (Map.Entry<DocumentModel, String> entry : docModelsToUpdate.entrySet()) {
-            DocumentModel dmodel = entry.getKey();
-            String newCurrentLocationValue = entry.getValue();
-            dmodel.setProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY, newCurrentLocationValue);
-            coreSession.saveDocument(dmodel);
-            collectionObjectsUpdated++;
+            // ... update that value and then save the updated CollectionObject.
+            collectionObjectDocModel.setProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY, computedCurrentLocationRefName);
+            coreSession.saveDocument(collectionObjectDocModel);
             if (logger.isTraceEnabled()) {
                 String afterUpdateComputedCurrentLocationRefName =
-                        (String) dmodel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY);
+                        (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_COMMON_SCHEMA, COMPUTED_CURRENT_LOCATION_PROPERTY);
                 logger.trace("Following update, new computedCurrentLocation refName value=" + afterUpdateComputedCurrentLocationRefName);
 
             }
-        }
-        logger.info("Updated " + collectionObjectsUpdated + " CollectionObject record(s) with new computed current location(s).");
-    }
-
-    // FIXME: Generic methods like many of those below might be split off,
-    // into an event utilities class, base classes, or otherwise. - ADR 2012-12-05
-    //
-    // FIXME: Identify whether the equivalent of the documentMatchesType utility
-    // method is already implemented and substitute a call to the latter if so.
-    // This may well already exist.
-    /**
-     * Identifies whether a document matches a supplied document type.
-     *
-     * @param docModel a document model.
-     * @param docType a document type string.
-     * @return true if the document matches the supplied document type; false if
-     * it does not.
-     */
-    private boolean documentMatchesType(DocumentModel docModel, String docType) {
-        if (docModel == null || Tools.isBlank(docType)) {
-            return false;
-        }
-        if (docModel.getType().startsWith(docType)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Identifies whether a document is an active document; that is, if it is
-     * not a versioned record; not a proxy (symbolic link to an actual record);
-     * and not in the 'deleted' workflow state.
-     *
-     * (A note relating the latter: Nuxeo appears to send 'documentModified'
-     * events even on workflow transitions, such when records are 'soft deleted'
-     * by being transitioned to the 'deleted' workflow state.)
-     *
-     * @param docModel
-     * @return true if the document is an active document; false if it is not.
-     */
-    private boolean isActiveDocument(DocumentModel docModel) {
-        if (docModel == null) {
-            return false;
-        }
-        boolean isActiveDocument = false;
-        try {
-            if (!docModel.isVersion()
-                    && !docModel.isProxy()
-                    && !docModel.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
-                isActiveDocument = true;
-            }
-        } catch (ClientException ce) {
-            logger.warn("Error while identifying whether document is an active document: ", ce);
-        }
-        return isActiveDocument;
-    }
-
-    /**
-     * Returns a document model for a record identified by a CSID.
-     *
-     * @param session a repository session.
-     * @param collectionObjectCsid a CollectionObject identifier (CSID)
-     * @return a document model for the record identified by the supplied CSID.
-     */
-    private DocumentModel getDocModelFromCsid(CoreSession session, String collectionObjectCsid) {
-        DocumentModelList collectionObjectDocModels = null;
-        try {
-            final String query = "SELECT * FROM "
-                    + NuxeoUtils.BASE_DOCUMENT_TYPE
-                    + " WHERE "
-                    + NuxeoUtils.getByNameWhereClause(collectionObjectCsid);
-            collectionObjectDocModels = session.query(query);
-        } catch (Exception e) {
-            logger.warn("Exception in query to get document model for CollectionObject: ", e);
-        }
-        if (collectionObjectDocModels == null || collectionObjectDocModels.isEmpty()) {
-            logger.warn("Could not get document models for CollectionObject(s).");
-            return null;
-        } else if (collectionObjectDocModels.size() != 1) {
-            logger.debug("Found more than 1 document with CSID=" + collectionObjectCsid);
-            return null;
-        }
-        return collectionObjectDocModels.get(0);
-    }
-
-    // FIXME: A quick first pass, using an only partly query-based technique for
-    // getting the current location, augmented by procedural code.
-    //
-    // Should be replaced by a more performant method, based entirely, or nearly so,
-    // on a query.
-    //
-    // E.g. the following is a sample CMIS query for retrieving Movement records
-    // related to a CollectionObject, which might serve as the basis for that query.
-    /*
-     "SELECT DOC.nuxeo:pathSegment, DOC.dc:title, REL.dc:title,"
-     + "REL.relations_common:objectCsid, REL.relations_common:subjectCsid FROM Movement DOC "
-     + "JOIN Relation REL ON REL.relations_common:objectCsid = DOC.nuxeo:pathSegment "
-     + "WHERE REL.relations_common:subjectCsid = '5b4c617e-53a0-484b-804e' "
-     + "AND DOC.nuxeo:isVersion = false "
-     + "ORDER BY DOC.collectionspace_core:updatedAt DESC";
-     */
-    /**
-     * Returns the computed current location for a CollectionObject.
-     *
-     * @param session a repository session.
-     * @param collectionObjectCsid a CollectionObject identifier (CSID)
-     * @throws ClientException
-     * @return the computed current location for the CollectionObject identified
-     * by the supplied CSID.
-     */
-    private String computeCurrentLocation(CoreSession session, String collectionObjectCsid)
-            throws ClientException {
-        String computedCurrentLocation = "";
-        // Get Relation records for Movements related to this CollectionObject.
-        //
-        // Some values below are hard-coded for readability, rather than
-        // being obtained from constants.
-        String query = String.format(
-                "SELECT * FROM %1$s WHERE " // collectionspace_core:tenantId = 1 "
-                + "("
-                + "  (%2$s:subjectCsid = '%3$s' "
-                + "  AND %2$s:objectDocumentType = '%4$s') "
-                + " OR "
-                + "  (%2$s:objectCsid = '%3$s' "
-                + "  AND %2$s:subjectDocumentType = '%4$s') "
-                + ")"
-                + ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT,
-                RELATION_DOCTYPE, RELATIONS_COMMON_SCHEMA, collectionObjectCsid, MOVEMENT_DOCTYPE);
-        if (logger.isTraceEnabled()) {
-            logger.trace("query=" + query);
-        }
-        DocumentModelList relatedDocModels = session.query(query);
-        if (relatedDocModels == null || relatedDocModels.isEmpty()) {
-            logger.warn("Unexpectedly found no Movement records related to this CollectionObject record.");
-            return computedCurrentLocation;
         } else {
             if (logger.isTraceEnabled()) {
-                logger.trace("Found " + relatedDocModels.size() + " Movement record(s) related to this CollectionObject record.");
-            }
-        }
-        // Iterate through related movement records, to get the CollectionObject's
-        // computed current location from the related Movement record with the
-        // most recent location date.
-        GregorianCalendar mostRecentLocationDate = EARLIEST_COMPARISON_DATE;
-        DocumentModel movementDocModel = null;
-        String csid = "";
-        String location = "";
-        for (DocumentModel relatedDocModel : relatedDocModels) {
-            // Due to the 'OR' operator in the query above, related Movement
-            // record CSIDs may reside in either the subject or object CSID fields
-            // of the relation record. Whichever CSID value doesn't match the
-            // CollectionObject's CSID is inferred to be the related Movement record's CSID.
-            csid = (String) relatedDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
-            if (csid.equals(collectionObjectCsid)) {
-                csid = (String) relatedDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+                logger.trace("computedCurrentLocation refName does NOT require updating.");
             }
-            movementDocModel = getDocModelFromCsid(session, csid);
-            if (movementDocModel == null) {
-                continue;
-            }
-            if (logger.isTraceEnabled()) {
-                logger.trace("Movement CSID=" + NuxeoUtils.getCsid(movementDocModel));
-            }
-            // Verify that the Movement record is active. This will also exclude
-            // versioned Movement records from the computation of the current
-            // location, for tenants that are versioning such records.
-            if (!isActiveDocument(movementDocModel)) {
-                if (logger.isTraceEnabled()) {
-                    logger.trace("Skipping this inactive Movement record ...");
-                }
-                continue;
-            }
-            GregorianCalendar locationDate =
-                    (GregorianCalendar) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, LOCATION_DATE_PROPERTY);
-            if (locationDate == null) {
-                continue;
-            }
-            if (locationDate.after(mostRecentLocationDate)) {
-                mostRecentLocationDate = locationDate;
-                location = (String) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, CURRENT_LOCATION_PROPERTY);
-            }
-            if (Tools.notBlank(location)) {
-                computedCurrentLocation = location;
-            }
-        }
-        return computedCurrentLocation;
-    }
-
-    /**
-     * Returns the CSID for a desired document type from a Relation record,
-     * where the relationship involves two specified, different document types.
-     *
-     * @param relationDocModel a document model for a Relation record.
-     * @param desiredDocType a desired document type.
-     * @param relatedDocType a related document type.
-     * @throws ClientException
-     * @return the CSID from the desired document type in the relation. Returns
-     * an empty string if the Relation record does not involve both the desired
-     * and related document types, or if the desired document type is at both
-     * ends of the relation.
-     */
-    private String getCsidForDesiredDocType(DocumentModel relationDocModel,
-            String desiredDocType, String relatedDocType) throws ClientException {
-        String csid = "";
-        String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
-        String objectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
-        if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(desiredDocType)) {
-            return csid;
-        }
-        if (subjectDocType.startsWith(desiredDocType) && objectDocType.startsWith(relatedDocType)) {
-            csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
-        } else if (subjectDocType.startsWith(relatedDocType) && objectDocType.startsWith(desiredDocType)) {
-            csid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
         }
-        return csid;
     }
 }
\ No newline at end of file