From 0bb9350513822d7cd8683b5427fe9abd77e51e4e Mon Sep 17 00:00:00 2001 From: remillet Date: Fri, 27 Jan 2017 17:11:50 -0800 Subject: [PATCH] CSPACE-7062: Add support for a 'no context' invocation of the UpdateObjectLocation batch job. --- .../AbstractUpdateObjectLocationValues.java | 169 ++++++++++++++++-- .../listener/UpdateObjectLocationOnMove.java | 10 ++ .../jaxb/src/main/resources/batch_common.xsd | 5 +- .../batch/AbstractBatchInvocable.java | 22 ++- .../services/batch/BatchInvocable.java | 4 - .../services/batch/BatchResource.java | 5 +- .../nuxeo/BatchDocumentModelHandler.java | 11 +- .../nuxeo/CreateAndLinkLoanOutBatchJob.java | 41 +++-- .../nuxeo/UpdateObjectLocationBatchJob.java | 124 +++++++++++-- .../services/client/IClientQueryParams.java | 2 +- .../services/common/api/Tools.java | 4 + .../context/AbstractServiceContextImpl.java | 19 ++ .../common/context/ServiceContext.java | 6 + .../services/common/invocable/Invocable.java | 10 ++ .../client/java/DocumentModelHandler.java | 15 +- .../src/main/resources/invocationContext.xsd | 1 + 16 files changed, 391 insertions(+), 57 deletions(-) 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 index b984f2e8c..772469da8 100644 --- 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 @@ -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. * diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java b/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java index 1b9b6f04b..4be3f82bb 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java +++ b/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java @@ -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 diff --git a/services/batch/jaxb/src/main/resources/batch_common.xsd b/services/batch/jaxb/src/main/resources/batch_common.xsd index 4fe6ab3b4..7b9eebd44 100644 --- a/services/batch/jaxb/src/main/resources/batch_common.xsd +++ b/services/batch/jaxb/src/main/resources/batch_common.xsd @@ -32,7 +32,10 @@ - + + + + diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java index b2252572a..d5f0b7491 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/AbstractBatchInvocable.java @@ -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 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 diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java index 724b70932..9e9f5cca3 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchInvocable.java @@ -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; diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java index 8b37cb046..c9aedc528 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/BatchResource.java @@ -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 ctx = createServiceContext(); + ServiceContext ctx = createServiceContext(ui); BatchDocumentModelHandler handler = (BatchDocumentModelHandler)createDocumentHandler(ctx); return handler.invokeBatchJob(ctx, csid, resourceMap, invContext); diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java index 1dc994666..5509ebf0c 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/BatchDocumentModelHandler.java @@ -58,12 +58,12 @@ public class BatchDocumentModelHandler extends NuxeoDocumentModelHandler 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 forDocTypeList = (List) 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 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 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 { + ""+loanCSID+"" + ""+LOAN_DOCTYPE+"" + ""+toCSID+"" - + ""+context.getDocType()+"" + + ""+invocationCtx.getDocType()+"" + ""+RELATION_TYPE+"" + ""+RELATION_PREDICATE_DISP+"" + ""; diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateObjectLocationBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateObjectLocationBatchJob.java index 572df62c2..0e510335c 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateObjectLocationBatchJob.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/UpdateObjectLocationBatchJob.java @@ -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 resultSet = new HashSet(); + + 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 alreadyProcessedMovementCsids = new HashSet(); @@ -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 existingList, AbstractCommonList abstractCommonList) { + for (AbstractCommonList.ListItem listitem : abstractCommonList.getListItem()) { + existingList.add(AbstractCommonListUtils.ListItemGetCSID(listitem)); + } + } + private List 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 noContextCsids = getCsidsList(collectionObjects); + + boolean morePages = true; + long currentPage = 0; + long totalItems = 0; + long pageSize = DEFAULT_PAGE_SIZE; + List noContextCsids = new ArrayList(); + + 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; } } diff --git a/services/client/src/main/java/org/collectionspace/services/client/IClientQueryParams.java b/services/client/src/main/java/org/collectionspace/services/client/IClientQueryParams.java index 24865448c..455e446b4 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/IClientQueryParams.java +++ b/services/client/src/main/java/org/collectionspace/services/client/IClientQueryParams.java @@ -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"; } diff --git a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java index 9981244a6..4a6b7f064 100644 --- a/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java +++ b/services/common-api/src/main/java/org/collectionspace/services/common/api/Tools.java @@ -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. diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java index f5c0c0d95..9679a7177 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java @@ -201,6 +201,25 @@ public abstract class AbstractServiceContextImpl 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 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() */ diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java index eaff58c8c..2eea17d9a 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java @@ -100,6 +100,12 @@ public interface ServiceContext { */ 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(); */ diff --git a/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java b/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java index 8c498d86e..efd24294d 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java +++ b/services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java @@ -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(); } diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/DocumentModelHandler.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/DocumentModelHandler.java index 9e0895af6..ba8eef040 100644 --- a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/DocumentModelHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/DocumentModelHandler.java @@ -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 // // 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)); + } } } diff --git a/services/jaxb/src/main/resources/invocationContext.xsd b/services/jaxb/src/main/resources/invocationContext.xsd index 8c8ed9cec..1665c8ab2 100644 --- a/services/jaxb/src/main/resources/invocationContext.xsd +++ b/services/jaxb/src/main/resources/invocationContext.xsd @@ -20,6 +20,7 @@ + -- 2.47.3