]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-7062: Add support for a 'no context' invocation of the UpdateObjectLocation...
authorremillet <remillet@yahoo.com>
Sat, 28 Jan 2017 01:11:50 +0000 (17:11 -0800)
committerremillet <remillet@yahoo.com>
Sat, 28 Jan 2017 01:11:50 +0000 (17:11 -0800)
16 files changed:
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/AbstractUpdateObjectLocationValues.java
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java
services/batch/jaxb/src/main/resources/batch_common.xsd
services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java
services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java
services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java
services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java
services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java
services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateObjectLocationBatchJob.java
services/client/src/main/java/org/collectionspace/services/client/IClientQueryParams.java
services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java
services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java
services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java
services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/DocumentModelHandler.java
services/jaxb/src/main/resources/invocationContext.xsd

index b984f2e8c0b9b1d5f262c5c5c65dd7260b6d3943..772469da8e9bb9b7dd8d352ab7a62d0fe552a209 100644 (file)
@@ -6,9 +6,10 @@ import java.util.Set;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-
+import org.collectionspace.services.client.AbstractCommonListUtils;
 import org.collectionspace.services.client.workflow.WorkflowClient;
 import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.common.api.Tools.NoRelatedRecordsException;
 import org.collectionspace.services.movement.nuxeo.MovementConstants;
 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
 import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper;
@@ -18,6 +19,8 @@ import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
 import org.nuxeo.ecm.core.api.DocumentModelList;
 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
+import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
+import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
 import org.nuxeo.ecm.core.event.Event;
 import org.nuxeo.ecm.core.event.EventContext;
 import org.nuxeo.ecm.core.event.EventListener;
@@ -41,6 +44,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
     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 CURRENT_LOCATION_ELEMENT_NAME = "currentLocation"; // From movement_commons schema.  FIXME: Get from external constant that already exists
     protected 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
@@ -50,7 +54,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
     protected final static String UPDATED_AT_PROPERTY = "updatedAt"; // FIXME: Get from external constant
     private final static String NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
             "AND ecm:isCheckedInVersion = 0"
-            + "AND ecm:isProxy = 0 ";
+            + " AND ecm:isProxy = 0 ";
     private final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
             "AND (ecm:currentLifeCycleState <> 'deleted') "
             + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
@@ -223,11 +227,19 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
                 continue;
             }
             // Get the CollectionObject's most recent, related Movement.
-            mostRecentMovementDocModel = getMostRecentMovement(coreSession, collectionObjectCsid,
-                    isAboutToBeRemovedEvent, movementCsidToFilter);
-            if (mostRecentMovementDocModel == null) {
-                continue;
-            }
+            
+            try {
+                               mostRecentMovementDocModel = getMostRecentMovement(coreSession, collectionObjectCsid,
+                                       isAboutToBeRemovedEvent, movementCsidToFilter);
+                   if (mostRecentMovementDocModel == null) {
+                       // This means we couldn't figure out which Movement record to use.
+                       continue;
+                   }
+                       } catch (NoRelatedRecordsException e) {
+                               // This means there were NO active Movement records to use.
+                               mostRecentMovementDocModel = new DocumentModelImpl(MOVEMENT_DOCTYPE); // Create an empty document model
+                       }
+            
             // Update the CollectionObject with values from that Movement.
             collectionObjectDocModel =
                     updateCollectionObjectValuesFromMovement(collectionObjectDocModel, mostRecentMovementDocModel);
@@ -237,6 +249,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
                         COMPUTED_CURRENT_LOCATION_PROPERTY);
                 logger.trace("computedCurrentLocation refName after value update=" + computedCurrentLocationRefName);
             }
+            
             coreSession.saveDocument(collectionObjectDocModel);
         }
     }
@@ -357,7 +370,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
      * CSID.
      */
     protected static DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String collectionObjectCsid) {
-        DocumentModelList collectionObjectDocModels = null;
+        DocumentModelList docModelList = null;
         try {
             final String query = "SELECT * FROM "
                     + NuxeoUtils.BASE_DOCUMENT_TYPE
@@ -365,18 +378,18 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
                     + NuxeoUtils.getByNameWhereClause(collectionObjectCsid)
                     + " "
                     + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
-            collectionObjectDocModels = session.query(query);
+            docModelList = session.query(query);
         } catch (Exception e) {
             logger.warn("Exception in query to get active document model for CollectionObject: ", e);
         }
-        if (collectionObjectDocModels == null || collectionObjectDocModels.isEmpty()) {
+        if (docModelList == null || docModelList.isEmpty()) {
             logger.warn("Could not get active document models for CollectionObject(s).");
             return null;
-        } else if (collectionObjectDocModels.size() != 1) {
+        } else if (docModelList.size() != 1) {
             logger.debug("Found more than 1 active document with CSID=" + collectionObjectCsid);
             return null;
         }
-        return collectionObjectDocModels.get(0);
+        return docModelList.get(0);
     }
 
     // FIXME: A quick first pass, using an only partly query-based technique for
@@ -415,7 +428,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
      */
     protected static DocumentModel getMostRecentMovement(CoreSessionInterface session, String collectionObjectCsid,
             boolean isAboutToBeRemovedEvent, String aboutToBeRemovedMovementCsidToFilter)
-            throws ClientException {
+            throws ClientException, Tools.NoRelatedRecordsException {
         DocumentModel mostRecentMovementDocModel = null;
         // Get Relation records for Movements related to this CollectionObject.
         //
@@ -444,6 +457,34 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
                 logger.trace("Found " + relationDocModels.size() + " relations to Movement records to/from this CollectionObject record.");
             }
         }
+        
+        //
+        // Remove redundant document models from the list.
+        //
+        relationDocModels = removeRedundantRelations(relationDocModels);
+        
+        //
+        // Remove relationships with inactive movement records
+        //
+        relationDocModels = removeRelationsWithInactiveMovements(session, relationDocModels);
+        
+        if (relationDocModels == null || relationDocModels.size() == 0) throw new Tools.NoRelatedRecordsException();
+        
+        //
+        // If there is only one related movement record, then return it as the most recent
+        // movement record -if it's current location element is not empty.
+        //
+        if (relationDocModels.size() == 1) {
+               DocumentModel relationDocModel = relationDocModels.get(0);
+               DocumentModel movementDocModel = getMovementDocModelFromRelation(session, relationDocModel);
+            String currentLocation = (String) movementDocModel.getProperty(MOVEMENTS_COMMON_SCHEMA, CURRENT_LOCATION_ELEMENT_NAME);
+            if (Tools.isBlank(currentLocation) || !isActiveDocument(movementDocModel)) { // currentLocation must be set and record must be active
+               movementDocModel = null;
+            }
+                        
+            return movementDocModel;
+        }
+        
         // Iterate through related movement records, to find the related
         // Movement record with the most recent location date.
         GregorianCalendar mostRecentLocationDate = EARLIEST_COMPARISON_DATE;
@@ -531,10 +572,110 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
                 }
             }
         }
+        
         return mostRecentMovementDocModel;
     }
+    
+       //
+    // This method assumes that the relation passed into this method is between a Movement record
+    // and a CollectionObject (cataloging) record.
+    //
+    private static DocumentModel getMovementDocModelFromRelation(CoreSessionInterface session, DocumentModel relationDocModel) {
+       String movementCsid = null;
+       
+        String subjectDocType = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
+        if (subjectDocType.endsWith(MOVEMENT_DOCTYPE)) {
+               movementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
+        } else {
+               movementCsid = (String) relationDocModel.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+        }
 
-    /**
+               return getCurrentDocModelFromCsid(session, movementCsid);
+       }
+
+       //
+    // Compares two Relation document models to see if they're either identical or
+    // reciprocal equivalents. 
+    //
+    private static boolean compareRelationDocModels(DocumentModel r1, DocumentModel r2) {
+       boolean result = false;
+       
+        String r1_subjectDocType = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
+        String r1_objectDocType = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
+        String r1_subjectCsid = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
+        String r1_objectCsid = (String) r1.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+
+        String r2_subjectDocType = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_DOCTYPE_PROPERTY);
+        String r2_objectDocType = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_DOCTYPE_PROPERTY);
+        String r2_subjectCsid = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, SUBJECT_CSID_PROPERTY);
+        String r2_objectCsid = (String) r2.getProperty(RELATIONS_COMMON_SCHEMA, OBJECT_CSID_PROPERTY);
+        
+        // Check to see if they're identical
+        if (r1_subjectDocType.equalsIgnoreCase(r2_subjectDocType) && r1_objectDocType.equalsIgnoreCase(r2_objectDocType)
+                       && r1_subjectCsid.equalsIgnoreCase(r2_subjectCsid) && r1_objectCsid.equalsIgnoreCase(r2_objectCsid)) {
+               return true;
+        }
+        
+        // Check to see if they're reciprocal
+        if (r1_subjectDocType.equalsIgnoreCase(r2_objectDocType) && r1_objectDocType.equalsIgnoreCase(r2_subjectDocType)
+                       && r1_subjectCsid.equalsIgnoreCase(r2_objectCsid) && r1_objectCsid.equalsIgnoreCase(r2_subjectCsid)) {
+               return true;
+        }
+
+       return result;
+    }
+
+    //
+    // Return a Relation document model list with redundant (either identical or reciprocal) relations removed.
+    //
+    private static DocumentModelList removeRedundantRelations(DocumentModelList relationDocModelList) {
+       DocumentModelList resultList = new DocumentModelListImpl();
+       for (DocumentModel relationDocModel : relationDocModelList) {
+               if (existsInResultList(resultList, relationDocModel) == false) {
+                       resultList.add(relationDocModel);
+               }
+       }
+               // TODO Auto-generated method stub
+               return resultList;
+       }
+    
+    //
+    // Return just the list of relationships with active Movement records
+    //
+    private static DocumentModelList removeRelationsWithInactiveMovements(CoreSessionInterface session, DocumentModelList relationDocModelList) {
+       DocumentModelList resultList = new DocumentModelListImpl();
+       
+       for (DocumentModel relationDocModel : relationDocModelList) {
+            String movementCsid = getCsidForDesiredDocTypeFromRelation(relationDocModel, MOVEMENT_DOCTYPE, COLLECTIONOBJECT_DOCTYPE);
+            DocumentModel movementDocModel = getCurrentDocModelFromCsid(session, movementCsid);
+            if (isActiveDocument(movementDocModel) == true) {
+                       resultList.add(relationDocModel);
+            } else {
+               logger.trace(String.format("Disqualified relationship with inactive Movement record=%s.", movementCsid));
+            }
+       }
+       
+               return resultList;
+       }
+    
+
+    //
+    // Check to see if the Relation (or its equivalent reciprocal) is already in the list.
+    //
+       private static boolean existsInResultList(DocumentModelList relationDocModelList, DocumentModel relationDocModel) {
+               boolean result = false;
+               
+       for (DocumentModel target : relationDocModelList) {
+               if (compareRelationDocModels(relationDocModel, target) == true) {
+                       result = true;
+                       break;
+               }
+       }
+               
+               return result;
+       }
+
+       /**
      * Returns the CSID for a desired document type from a Relation record,
      * where the relationship involves two specified, different document types.
      *
index 1b9b6f04b88625229bfeb846039b1854367ee1d7..4be3f82bb452bb66bacc40ce69052bbd6d6fbcf4 100644 (file)
@@ -27,6 +27,15 @@ public class UpdateObjectLocationOnMove extends AbstractUpdateObjectLocationValu
     protected DocumentModel updateComputedCurrentLocationValue(DocumentModel collectionObjectDocModel,
             DocumentModel movementDocModel)
             throws ClientException {
+       
+       //
+       // First see if we're being asked to clear the computed location field
+       //
+       if (movementDocModel.getCoreSession() == null) {
+               collectionObjectDocModel.setProperty(COLLECTIONOBJECTS_COMMON_SCHEMA,
+                    COMPUTED_CURRENT_LOCATION_PROPERTY, null);
+               return collectionObjectDocModel;
+       }
 
         // Get the current location value from the Movement (the "new" value)
         String currentLocationRefName =
@@ -79,6 +88,7 @@ public class UpdateObjectLocationOnMove extends AbstractUpdateObjectLocationValu
                 logger.trace("computedCurrentLocation refName does NOT require updating.");
             }
         }
+        
         return collectionObjectDocModel;
     }
 }
\ No newline at end of file
index 4fe6ab3b43d90f5f492d7bd062a13562df373477..7b9eebd446f22bb907c031efa58883a23cd63f03 100644 (file)
                                        </xs:complexType>
                                </xs:element>
                                <xs:element name="supportsNoContext" type="xs:boolean" />
-                               <xs:element name="forSingleDoc" type="xs:boolean" />
+                               <xs:element name="supportsSingleDoc" type="xs:boolean" />
+                               <xs:element name="supportsDocList" type="xs:boolean" />
+                               <xs:element name="supportsGroup" type="xs:boolean" />
+                               <!-- Batch specific fields -->
                                <xs:element name="createsNewFocus" type="xs:boolean" />
                                <xs:element name="className" type="xs:string" />
                        </xs:sequence>
index b2252572a8e790d13cf6f44361e3f51afa9b2672..d5f0b74916df87432c252b50a91ddd31bd2b864b 100644 (file)
@@ -4,6 +4,7 @@ import java.util.Collections;
 import java.util.List;
 import javax.ws.rs.core.Response;
 import org.collectionspace.services.common.ResourceMap;
+import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.invocable.InvocationContext;
 import org.collectionspace.services.common.invocable.InvocationResults;
 import org.slf4j.Logger;
@@ -34,7 +35,9 @@ public abstract class AbstractBatchInvocable implements BatchInvocable {
             "Could not find required CSID values in the invocation context for this batch job.";
     private List<String> invocationModes;
     private ResourceMap resourceMap;
-    private InvocationContext context;
+    private InvocationContext invocationCtx;
+    private ServiceContext ctx;
+
     private int completionStatus;
     private InvocationResults results;
     private InvocationError errorInfo;
@@ -47,7 +50,7 @@ public abstract class AbstractBatchInvocable implements BatchInvocable {
     private void init() {
         this.invocationModes = Collections.emptyList();
         this.resourceMap = null;
-        this.context = null;
+        this.invocationCtx = null;
         this.completionStatus = STATUS_UNSTARTED;
         this.results = new InvocationResults();
         this.errorInfo = null;
@@ -71,13 +74,24 @@ public abstract class AbstractBatchInvocable implements BatchInvocable {
         this.resourceMap = resourceMap;
     }
 
+    @Override
+    public void setServiceContext(ServiceContext context) {
+        this.ctx = context;
+    }
+    
+    @Override
+    public ServiceContext getServiceContext() {
+        return ctx;
+    }
+
+    @Override
     public InvocationContext getInvocationContext() {
-        return context;
+        return invocationCtx;
     }
 
     @Override
     public void setInvocationContext(InvocationContext context) {
-        this.context = context;
+        this.invocationCtx = context;
     }
 
     @Override
index 724b7093208b7a8f41757f6de4a5ca0fc194698d..9e9f5cca3f5123c43a2a00f83d07d1caba4ba710 100644 (file)
@@ -1,9 +1,5 @@
 package org.collectionspace.services.batch;
 
-import java.util.HashMap;
-import java.util.Set;
-
-import org.collectionspace.services.common.NuxeoBasedResource;
 import org.collectionspace.services.common.ResourceMap;
 import org.collectionspace.services.common.invocable.Invocable;
 
index 8b37cb0462fd48947d611e172e90ed5188c1cf4c..c9aedc5288b575317a3d6ad9fa2c7f55098934b0 100644 (file)
@@ -180,11 +180,12 @@ public class BatchResource extends NuxeoBasedResource {
     @POST
     @Path("{csid}")
     public InvocationResults invokeBatchJob(
-               @Context ResourceMap resourceMap, 
+               @Context ResourceMap resourceMap,
+               @Context UriInfo ui,
                @PathParam("csid") String csid,
                InvocationContext invContext) {
         try {
-            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext();
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
             BatchDocumentModelHandler handler = (BatchDocumentModelHandler)createDocumentHandler(ctx);
             
             return handler.invokeBatchJob(ctx, csid, resourceMap, invContext);
index 1dc994666c061925312bc356fdab8ad04a6eadf0..5509ebf0c51df896656779f90fd818f48e8d5940 100644 (file)
@@ -58,12 +58,12 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler<BatchCo
        protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode();
 
        public InvocationResults invokeBatchJob(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String csid,
-                       ResourceMap resourceMap, InvocationContext invContext) throws Exception {
+                       ResourceMap resourceMap, InvocationContext invocationCtx) throws Exception {
 
                CoreSessionInterface repoSession = null;
                boolean releaseRepoSession = false;
 
-               String invocationMode = invContext.getMode();
+               String invocationMode = invocationCtx.getMode();
                String modeProperty = null;
                boolean checkDocType = true;
                if (BatchInvocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) {
@@ -98,9 +98,9 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler<BatchCo
                        }
                        if (checkDocType) {
                                List<String> forDocTypeList = (List<String>) NuxeoUtils.getProperyValue(docModel, BatchJAXBSchema.FOR_DOC_TYPES); //docModel.getPropertyValue(BatchJAXBSchema.FOR_DOC_TYPES);
-                               if (forDocTypeList == null || !forDocTypeList.contains(invContext.getDocType())) {
+                               if (forDocTypeList == null || !forDocTypeList.contains(invocationCtx.getDocType())) {
                                        throw new BadRequestException("BatchResource: Invoked with unsupported document type: "
-                                                       + invContext.getDocType());
+                                                       + invocationCtx.getDocType());
                                }
                        }
                        className = (String) NuxeoUtils.getProperyValue(docModel, BatchJAXBSchema.BATCH_CLASS_NAME); //docModel.getPropertyValue(BatchJAXBSchema.BATCH_CLASS_NAME);
@@ -140,7 +140,8 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler<BatchCo
                        throw new BadRequestException("BatchResource: Invoked with unsupported context mode: " + invocationMode);
                }
 
-               batchInstance.setInvocationContext(invContext);
+               batchInstance.setInvocationContext(invocationCtx);
+               batchInstance.setServiceContext(ctx);
                
                if (resourceMap != null) {
                        batchInstance.setResourceMap(resourceMap);
index 815bf5f3e29a55714f4d0c8f0f3d181ac2dbb27f..1781753f1a4662f9b7113ed3c9ca03a8dc4c8e17 100644 (file)
@@ -1,7 +1,6 @@
 package org.collectionspace.services.batch.nuxeo;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 
 import javax.ws.rs.core.Response;
@@ -11,6 +10,7 @@ import org.collectionspace.services.client.CollectionSpaceClientUtils;
 import org.collectionspace.services.common.NuxeoBasedResource;
 import org.collectionspace.services.common.ResourceMap;
 import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
+import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.invocable.InvocationContext;
 import org.collectionspace.services.common.invocable.InvocationResults;
 import org.collectionspace.services.client.LoanoutClient;
@@ -19,7 +19,8 @@ import org.collectionspace.services.client.RelationClient;
 public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
 
        private static ArrayList<String> invocationModes = null;
-       private InvocationContext context;
+       private InvocationContext invocationCtx;
+       private ServiceContext ctx;
        private int completionStatus;
        private ResourceMap resourceMap;
        private InvocationResults results;
@@ -33,7 +34,7 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
        
        public CreateAndLinkLoanOutBatchJob() {
                CreateAndLinkLoanOutBatchJob.setupClassStatics();
-               context = null;
+               invocationCtx = null;
                completionStatus = STATUS_UNSTARTED;
                resourceMap = null;
                results = new InvocationResults();
@@ -55,17 +56,33 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
                return CreateAndLinkLoanOutBatchJob.invocationModes;
        }
        
+    @Override
+    public void setServiceContext(ServiceContext context) {
+        this.ctx = context;
+    }
+    
+    @Override
+    public ServiceContext getServiceContext() {
+        return ctx;
+    }
+
+    @Override
+    public InvocationContext getInvocationContext() {
+        return invocationCtx;
+    }    
+       
        /**
         * Sets the invocation context for the batch job. Called before run().
         * @param context an instance of InvocationContext.
         */
+    @Override
        public void setInvocationContext(InvocationContext context) {
-               this.context = context;
+               this.invocationCtx = context;
        }
 
        /**
         * Sets the invocation context for the batch job. Called before run().
-        * @param context an instance of InvocationContext.
+        * @param invocationCtx an instance of InvocationContext.
         */
        public void setResourceMap(ResourceMap resourceMap) {
                this.resourceMap = resourceMap;
@@ -80,16 +97,16 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
                try {
                        // First, create the Loanout
                        if(createLoan() != STATUS_ERROR) {
-                               if(INVOCATION_MODE_SINGLE.equalsIgnoreCase(context.getMode())) {
+                               if(INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationCtx.getMode())) {
                                        if(createRelation(results.getPrimaryURICreated(), 
-                                                                               context.getSingleCSID()) != STATUS_ERROR) {
+                                                                               invocationCtx.getSingleCSID()) != STATUS_ERROR) {
                                                results.setNumAffected(1);
                                                results.setUserNote("CreateAndLinkLoanOutBatchJob created new Loanout: "
-                                                               +results.getPrimaryURICreated()+" with a link to the passed "+context.getDocType());
+                                                               +results.getPrimaryURICreated()+" with a link to the passed "+invocationCtx.getDocType());
                                                completionStatus = STATUS_COMPLETE;
                                        }
-                               } else if(INVOCATION_MODE_LIST.equalsIgnoreCase(context.getMode())) {
-                                       InvocationContext.ListCSIDs listWrapper = context.getListCSIDs();
+                               } else if(INVOCATION_MODE_LIST.equalsIgnoreCase(invocationCtx.getMode())) {
+                                       InvocationContext.ListCSIDs listWrapper = invocationCtx.getListCSIDs();
                                        List<String> csids = listWrapper.getCsid();
                                        if(csids.size()==0) {
                                                completionStatus = STATUS_ERROR;
@@ -109,7 +126,7 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
                                        if(completionStatus!=STATUS_ERROR) {
                                                results.setNumAffected(nCreated);
                                                results.setUserNote("CreateAndLinkLoanOutBatchJob created new Loanout: "
-                                                               +results.getPrimaryURICreated()+" with "+nCreated+" link(s) to "+context.getDocType());
+                                                               +results.getPrimaryURICreated()+" with "+nCreated+" link(s) to "+invocationCtx.getDocType());
                                                completionStatus = STATUS_COMPLETE;
                                        }
                                }
@@ -156,7 +173,7 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable {
                        +   "<subjectCsid>"+loanCSID+"</subjectCsid>"
                        +   "<subjectDocumentType>"+LOAN_DOCTYPE+"</subjectDocumentType>"
                        +   "<objectCsid>"+toCSID+"</objectCsid>"
-                       +   "<objectDocumentType>"+context.getDocType()+"</objectDocumentType>"
+                       +   "<objectDocumentType>"+invocationCtx.getDocType()+"</objectDocumentType>"
                        +   "<relationshipType>"+RELATION_TYPE+"</relationshipType>"
                        +   "<predicateDisplayName>"+RELATION_PREDICATE_DISP+"</predicateDisplayName>"
                        + "</ns2:relations_common></document>";
index 572df62c24403ded0a961a88b75ace95efe98fb5..0e510335c5a5afa94de89cb4a7bab61d0eabd774 100644 (file)
@@ -17,6 +17,7 @@ import org.collectionspace.services.batch.AbstractBatchInvocable;
 import org.collectionspace.services.batch.UriInfoImpl;
 import org.collectionspace.services.client.AbstractCommonListUtils;
 import org.collectionspace.services.client.CollectionObjectClient;
+import org.collectionspace.services.client.IClientQueryParams;
 import org.collectionspace.services.client.IQueryManager;
 import org.collectionspace.services.client.MovementClient;
 import org.collectionspace.services.client.PoxPayloadOut;
@@ -25,8 +26,10 @@ import org.collectionspace.services.common.NuxeoBasedResource;
 import org.collectionspace.services.common.ResourceMap;
 import org.collectionspace.services.common.api.RefNameUtils;
 import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.invocable.InvocationResults;
 import org.collectionspace.services.jaxb.AbstractCommonList;
+
 import org.dom4j.DocumentException;
 //import org.jboss.resteasy.specimpl.UriInfoImpl;
 import org.jdom.Document;
@@ -62,6 +65,7 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
             Namespace.getNamespace(
             COLLECTIONOBJECTS_COMMON_NAMESPACE_PREFIX,
             COLLECTIONOBJECTS_COMMON_NAMESPACE_URI);
+       private static final int DEFAULT_PAGE_SIZE = 1000;
     private final boolean EXCLUDE_DELETED = true;
     private final String CLASSNAME = this.getClass().getSimpleName();
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
@@ -136,15 +140,22 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
         NuxeoBasedResource movementResource = (NuxeoBasedResource) resourcemap.get(MovementClient.SERVICE_NAME);
         String computedCurrentLocation;
-        int numUpdated = 0;
+        long numUpdated = 0;
+        long processed = 0;
 
+        long recordsToProcess = csids.size();
+        long logInterval = recordsToProcess / 10 + 2;
         try {
 
             // For each CollectionObject record
             for (String collectionObjectCsid : csids) {
-
-                // FIXME: Optionally set competition status here to
-                // indicate what percentage of records have been processed.
+               
+               // Log progress at INFO level
+               if (processed % logInterval == 0) {
+                       logger.info(String.format("Recalculated computed location for %d of %d cataloging records.",
+                                       processed, recordsToProcess));
+               }
+               processed++;
 
                 // Skip over soft-deleted CollectionObject records
                 //
@@ -198,6 +209,22 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         getResults().setNumAffected(numUpdated);
         return getResults();
     }
+    
+    //
+    // Returns the number of distinct/unique CSID values in the list
+    //
+    private int getNumberOfDistinceRecords(AbstractCommonList abstractCommonList) {
+       Set<String> resultSet = new HashSet<String>();
+       
+        for (AbstractCommonList.ListItem listItem : abstractCommonList.getListItem()) {
+               String csid = AbstractCommonListUtils.ListItemGetElementValue(listItem, CSID_ELEMENT_NAME);
+               if (!Tools.isBlank(csid)) {
+                   resultSet.add(csid);
+               }
+        }
+       
+        return resultSet.size();
+    }
 
     private AbstractCommonList.ListItem getMostRecentMovement(AbstractCommonList relatedMovements) {
         Set<String> alreadyProcessedMovementCsids = new HashSet<String>();
@@ -208,6 +235,20 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         String updateDate;
         String mostRecentLocationDate = "";
         String comparisonUpdateDate = "";
+        
+        //
+        // If there is only one related movement record, then return it as the most recent
+        // movement record -if it's current location element is not empty.
+        //
+        if (getNumberOfDistinceRecords(relatedMovements) == 1) {
+               mostRecentMovement = relatedMovements.getListItem().get(0);
+            currentLocation = AbstractCommonListUtils.ListItemGetElementValue(mostRecentMovement, CURRENT_LOCATION_ELEMENT_NAME);
+            if (Tools.isBlank(currentLocation)) {
+               mostRecentMovement = null;
+            }
+            return mostRecentMovement;
+        }
+        
         for (AbstractCommonList.ListItem movementListItem : relatedMovements.getListItem()) {
             movementCsid = AbstractCommonListUtils.ListItemGetElementValue(movementListItem, CSID_ELEMENT_NAME);
             if (Tools.isBlank(movementCsid)) {
@@ -276,6 +317,7 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
             }
 
         }
+        
         return mostRecentMovement;
     }
 
@@ -285,9 +327,10 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
     //
     // Note: any such values must first be exposed in Movement list items,
     // in turn via configuration in Services tenant bindings ("listResultsField").
-    protected int updateCollectionObjectValues(NuxeoBasedResource collectionObjectResource,
-            String collectionObjectCsid, AbstractCommonList.ListItem mostRecentMovement,
-            ResourceMap resourcemap, int numUpdated)
+    protected long updateCollectionObjectValues(NuxeoBasedResource collectionObjectResource,
+            String collectionObjectCsid,
+            AbstractCommonList.ListItem mostRecentMovement,
+            ResourceMap resourcemap, long numUpdated)
             throws DocumentException, URISyntaxException {
         PoxPayloadOut collectionObjectPayload;
         String computedCurrentLocation;
@@ -354,9 +397,12 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         if (logger.isTraceEnabled()) {
             logger.trace("Update payload: " + "\n" + collectionObjectUpdatePayload);
         }
-        byte[] response = collectionObjectResource.update(resourcemap, null, collectionObjectCsid,
+        
+        UriInfo uriInfo = this.setupQueryParamForUpdateRecords(); // Determines if we'll updated the updateAt and updatedBy core values
+        byte[] response = collectionObjectResource.update(resourcemap, uriInfo, collectionObjectCsid,
                 collectionObjectUpdatePayload);
         numUpdated++;
+        
         if (logger.isTraceEnabled()) {
             logger.trace("Computed current location value for CollectionObject " + collectionObjectCsid
                     + " was set to " + computedCurrentLocation);
@@ -407,6 +453,30 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         URI uri = new URI(null, null, null, queryString, null);
         return createUriInfo(uri.getRawQuery());
     }
+    
+    protected UriInfo setupQueryParamForUpdateRecords() throws URISyntaxException {
+       UriInfo result = null;
+       
+       //
+       // Check first to see if we've got a query param.  It will override any invocation context value
+       //
+       String updateCoreValues = (String) getServiceContext().getQueryParams().getFirst(IClientQueryParams.UPDATE_CORE_VALUES);
+       if (Tools.isBlank(updateCoreValues)) {
+               //
+               // Since there is no query param, let's check the invocation context
+               //
+               updateCoreValues = getInvocationContext().getUpdateCoreValues();                
+       }
+       
+       //
+       // If we found a value, then use it to create a query parameter
+       //
+       if (Tools.notBlank(updateCoreValues)) {
+               result = createUriInfo(IClientQueryParams.UPDATE_CORE_VALUES + "=" + updateCoreValues);
+       }
+       
+       return result;
+    }
 
     protected String getFieldElementValue(PoxPayloadOut payload, String partLabel, Namespace partNamespace, String fieldPath) {
         String value = null;
@@ -460,6 +530,16 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         uriInfo.getQueryParameters().add(WorkflowClient.WORKFLOW_QUERY_NONDELETED, Boolean.FALSE.toString());
         return uriInfo;
     }
+    
+    private UriInfo addFilterForPageSize(UriInfo uriInfo, long startPage, long pageSize) throws URISyntaxException {
+       if (uriInfo == null) {
+            uriInfo = createUriInfo();
+        }
+        uriInfo.getQueryParameters().addFirst(IClientQueryParams.START_PAGE_PARAM, Long.toString(startPage));
+        uriInfo.getQueryParameters().addFirst(IClientQueryParams.PAGE_SIZE_PARAM, Long.toString(pageSize));
+
+        return uriInfo;
+    }
 
     private AbstractCommonList getRecordsRelatedToCsid(NuxeoBasedResource resource, String csid,
             String relationshipDirection, boolean excludeDeletedRecords) throws URISyntaxException {
@@ -538,6 +618,13 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         }
         return csids;
     }
+    
+    private void appendItemsToCsidsList(List<String> existingList, AbstractCommonList abstractCommonList) {
+        for (AbstractCommonList.ListItem listitem : abstractCommonList.getListItem()) {
+               existingList.add(AbstractCommonListUtils.ListItemGetCSID(listitem));
+        }
+    }
+    
 
     private List<String> getMemberCsidsFromGroup(String serviceName, String groupCsid) throws URISyntaxException, DocumentException {
         ResourceMap resourcemap = getResourceMap();
@@ -559,8 +646,25 @@ public class UpdateObjectLocationBatchJob extends AbstractBatchInvocable {
         NuxeoBasedResource collectionObjectResource = (NuxeoBasedResource) resourcemap.get(CollectionObjectClient.SERVICE_NAME);
         UriInfo uriInfo = createUriInfo();
         uriInfo = addFilterToExcludeSoftDeletedRecords(uriInfo);
-        AbstractCommonList collectionObjects = collectionObjectResource.getList(uriInfo);
-        List<String> noContextCsids = getCsidsList(collectionObjects);
+
+        boolean morePages = true;
+        long currentPage = 0;
+        long totalItems = 0;
+        long pageSize = DEFAULT_PAGE_SIZE;
+        List<String> noContextCsids = new ArrayList<String>();
+        
+        while (morePages == true) {
+               uriInfo = addFilterForPageSize(uriInfo, currentPage, pageSize);
+               AbstractCommonList collectionObjects = collectionObjectResource.getList(uriInfo);
+               appendItemsToCsidsList(noContextCsids, collectionObjects);
+               
+               if (collectionObjects.getItemsInPage() == pageSize) { // We know we're at the last page when the number of items returned in the last request is less than the page size.
+                       currentPage++;
+               } else {
+                       morePages = false;                      
+               }
+        }
+        
         return noContextCsids;
     }
 }
index 24865448c111b76264b6099902d5b10ff2aa3cfd..455e446b42ab766a395e65b184d15b26d40aa51d 100644 (file)
@@ -32,5 +32,5 @@ public interface IClientQueryParams {
     public static final String START_PAGE_PARAM = "pgNum";
     public static final String ORDER_BY_PARAM = "sortBy";
     public static final String IMPORT_TIMEOUT_PARAM = "impTimout";
-    
+    public static final String UPDATE_CORE_VALUES = "updateCoreValues";
 }
index 9981244a6cd4d4652a187da1735c0234a162b343..4a6b7f06427184ad75b44c4b70ae69b488c15613 100644 (file)
@@ -35,6 +35,10 @@ import java.util.regex.Matcher;
  */
 public class Tools {
        
+       public static class NoRelatedRecordsException extends Exception {
+               
+       }
+       
        private static final String PROPERTY_VAR_REGEX = "\\$\\{([A-Za-z0-9_\\.]+)\\}";
        
     /** @return first glued to second with the separator string, at most one time - useful for appending paths.
index f5c0c0d95cbcffc722840857d48b87e1d9d49986..9679a717754cc066af310527b48a8577332dd53d 100644 (file)
@@ -201,6 +201,25 @@ public abstract class AbstractServiceContextImpl<IT, OT>
        return this.getTimeoutParam(uriInfo);
     }
 
+    /**
+     * Returns TRUE unless the "recordUpdates" query param is set with a value of either "false", "FALSE", or "0"
+     * @return
+     */
+    @Override
+    public boolean shouldUpdateCoreValues() {
+               boolean recordUpdates = true;
+               
+               MultivaluedMap<String, String> queryParams = getQueryParams();
+               String paramValue = queryParams.getFirst(IClientQueryParams.UPDATE_CORE_VALUES);
+               if (paramValue != null && paramValue.equalsIgnoreCase(Boolean.FALSE.toString())) { // Find our if the caller wants us to record updates
+                       recordUpdates = false;
+               } else if (paramValue != null && paramValue.equals(Long.toString(0))) {
+                       recordUpdates = false;
+               }
+               
+               return recordUpdates;
+    }
+    
     /* (non-Javadoc)
      * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel()
      */
index eaff58c8cd2654c7c1f3af4fca8558ff98319f6f..2eea17d9ac9a3cc77f5d93287e6454063cf87e85 100644 (file)
@@ -100,6 +100,12 @@ public interface ServiceContext<IT, OT> {
      */
     public SecurityContext getSecurityContext();
 
+    /**
+     * Returns TRUE unless the "recordUpdates" query param is set with a value of either "false", "FALSE", or "0"
+     * @return
+     */
+    public boolean shouldUpdateCoreValues();
+    
     /**
      * getTimeoutSecs();
      */
index 8c498d86ec948e6a7ca07e141d3821d4e5cba940..efd24294d494d2e066e30a54c6089be3961aa925 100644 (file)
@@ -25,6 +25,7 @@ package org.collectionspace.services.common.invocable;
 import org.collectionspace.services.common.invocable.InvocationContext;
 import java.util.List;
 import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.common.context.ServiceContext;
 
 /**
  * Invocable defines an interface for invocable jobs (batch, reports, exports,
@@ -108,4 +109,13 @@ public interface Invocable {
      * Will only be called if getCompletionStatus() returns STATUS_ERROR.
      */
     public InvocationError getErrorInfo();
+
+    /*
+     * Save a handle to the JAX-RS related service context
+     */
+       void setServiceContext(ServiceContext context);
+
+       ServiceContext getServiceContext();
+
+       InvocationContext getInvocationContext();
 }
index 9e0895af6f0c372ee33abc3413ad42a69596b573..ba8eef040b03e18786a126148b668d3acbfa7518 100644 (file)
@@ -31,6 +31,7 @@ import javax.ws.rs.core.MultivaluedMap;
 import org.apache.commons.lang.StringUtils;
 import org.collectionspace.services.client.Profiler;
 import org.collectionspace.services.client.CollectionSpaceClient;
+import org.collectionspace.services.client.IClientQueryParams;
 import org.collectionspace.services.client.IQueryManager;
 import org.collectionspace.services.client.IRelationsManager;
 import org.collectionspace.services.client.PoxPayloadIn;
@@ -60,6 +61,7 @@ import org.collectionspace.services.lifecycle.StateList;
 import org.collectionspace.services.lifecycle.TransitionDef;
 import org.collectionspace.services.lifecycle.TransitionDefList;
 import org.collectionspace.services.lifecycle.TransitionList;
+
 import org.nuxeo.ecm.core.NXCore;
 import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
@@ -67,6 +69,7 @@ import org.nuxeo.ecm.core.api.DocumentModelList;
 import org.nuxeo.ecm.core.api.model.PropertyException;
 import org.nuxeo.ecm.core.lifecycle.LifeCycle;
 import org.nuxeo.ecm.core.lifecycle.LifeCycleService;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -313,10 +316,14 @@ public abstract class DocumentModelHandler<T, TL>
             //
             // Add updatedAt timestamp and updateBy user
             //
-                       documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
-                                       CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT, now);
-                       documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
-                                       CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY, userId);
+                       if (ctx.shouldUpdateCoreValues() == true) { // Ensure that our caller wants us to record this update
+                               documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
+                                               CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT, now);
+                               documentModel.setProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
+                                               CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_BY, userId);
+                       } else {
+                               logger.debug(String.format("Document with CSID=%s updated %s by user %s", documentModel.getName(), now, userId));
+                       }
                }               
     }
     
index 8c8ed9cec69662edc7ae4f5318bd24f080ef1d89..1665c8ab2b64440afc31f71848631a3af1f9c4de 100644 (file)
@@ -20,6 +20,7 @@
                <xs:complexType>
                        <xs:sequence>
                                <xs:element name="mode" type="xs:string"/>
+                               <xs:element name="updateCoreValues" type="xs:string"/>                          
                                <xs:element name="docType" type="xs:string"/>
                                <xs:element name="singleCSID" type="xs:string"/>
                                <xs:element name="groupCSID" type="xs:string"/>