From c201ffb60e0ec13f32c60fcebc4c35595c1d2c3f Mon Sep 17 00:00:00 2001 From: Patrick Schmitz Date: Sat, 28 May 2011 02:11:08 +0000 Subject: [PATCH] CSPACE-3900 More support for invoking Batch processes. Refactored the invocation slightly to support better error reporting, and to allow a given Batch job to handle various invocation modes. Extended the demo batch job to handle single and list invocation, and to actually create the LoanOut and the relations to the passed CSID(s). Added a series of tests to verify proper functioning and error handling for bad invocation, empty lists, etc. Fixed an odd exception in the ClientUtils, in which a URI cannot be cast to a String. --- .../test-data/xmlreplay/batch/batch.xml | 92 ++++++++----- .../test-data/xmlreplay/batch/batch1.xml | 3 +- .../xmlreplay/batch/batchBadInvContext.xml | 12 ++ .../batch/batchBadInvContextList.xml | 11 ++ .../xmlreplay/batch/batchInvContextList.xml | 12 ++ .../xmlreplay/batch/batchInvContextSingle.xml | 10 ++ .../test-data/xmlreplay/xml-replay-master.xml | 1 + .../main/resources/schemas/batch_common.xsd | 5 +- .../services/BatchJAXBSchema.java | 5 +- .../services/batch/BatchResource.java | 41 +++++- .../nuxeo/CreateAndLinkLoanOutBatchJob.java | 121 ++++++++++++++++-- .../client/CollectionSpaceClientUtils.java | 18 ++- .../services/common/invocable/Invocable.java | 28 +++- .../src/main/resources/invocationContext.xsd | 2 +- 14 files changed, 308 insertions(+), 53 deletions(-) create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContext.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContextList.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextList.xml create mode 100644 services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextSingle.xml diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch.xml index cce9951da..9a61e97b6 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch.xml @@ -1,42 +1,72 @@ - - - YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y - YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y - - - - - POST - /cspace-services/batch/ - batch/batch1.xml - - - POST - /cspace-services/collectionobjects/ - batch/collObj1.xml - - - POST - /cspace-services/batch/${ba1.CSID} - batch/batch1InvContext.xml - - ${ba2.CSID} - - - - + + + YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y + YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y + + + + POST + /cspace-services/batch/ + batch/batch1.xml + + + POST + /cspace-services/batch/ + batch/batch1.xml + + + POST + /cspace-services/collectionobjects/ + batch/collObj1.xml + + + POST + /cspace-services/batch/${createCollObj1.CSID} + batch/batchInvContextSingle.xml + + ${createBatch.CSID} + + + + POST + /cspace-services/batch/${createCollObj1.CSID} + batch/batchInvContextList.xml + + ${createBatch.CSID} + + + + POST + 400 + /cspace-services/batch/${createCollObj1.CSID} + batch/batchBadInvContext.xml + + ${createBatch.CSID} + + + + POST + 400 + /cspace-services/batch/${createCollObj1.CSID} + batch/batchBadInvContextList.xml + + ${createBatch.CSID} + + + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch1.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch1.xml index e68dd183e..7e6f5ef8d 100644 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch1.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batch1.xml @@ -6,7 +6,8 @@ TestCreateAndLinkLoanOutBatchJob This should be interesting CollectionObject - true + true + true true org.collectionspace.services.batch.nuxeo.CreateAndLinkLoanOutBatchJob diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContext.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContext.xml new file mode 100644 index 000000000..f07f70890 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContext.xml @@ -0,0 +1,12 @@ + + + garbage + CollectionObject + + ${CollObj1} + + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContextList.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContextList.xml new file mode 100644 index 000000000..007b010bc --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchBadInvContextList.xml @@ -0,0 +1,11 @@ + + + list + CollectionObject + + + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextList.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextList.xml new file mode 100644 index 000000000..26b00693a --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextList.xml @@ -0,0 +1,12 @@ + + + list + CollectionObject + + ${CollObj1} + + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextSingle.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextSingle.xml new file mode 100644 index 000000000..fb3ac7bb6 --- /dev/null +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/batch/batchInvContextSingle.xml @@ -0,0 +1,10 @@ + + + single + CollectionObject + ${CollObj1} + + + diff --git a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/xml-replay-master.xml b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/xml-replay-master.xml index 78f961dcd..fdd89e3da 100755 --- a/services/IntegrationTests/src/test/resources/test-data/xmlreplay/xml-replay-master.xml +++ b/services/IntegrationTests/src/test/resources/test-data/xmlreplay/xml-replay-master.xml @@ -46,6 +46,7 @@ + diff --git a/services/batch/3rdparty/nuxeo-platform-cs-batch/src/main/resources/schemas/batch_common.xsd b/services/batch/3rdparty/nuxeo-platform-cs-batch/src/main/resources/schemas/batch_common.xsd index 6ce08a59f..3e5b3f7c5 100644 --- a/services/batch/3rdparty/nuxeo-platform-cs-batch/src/main/resources/schemas/batch_common.xsd +++ b/services/batch/3rdparty/nuxeo-platform-cs-batch/src/main/resources/schemas/batch_common.xsd @@ -25,7 +25,10 @@ - + + + + diff --git a/services/batch/jaxb/src/main/java/org/collectionspace/services/BatchJAXBSchema.java b/services/batch/jaxb/src/main/java/org/collectionspace/services/BatchJAXBSchema.java index 122e79eb0..da083363c 100644 --- a/services/batch/jaxb/src/main/java/org/collectionspace/services/BatchJAXBSchema.java +++ b/services/batch/jaxb/src/main/java/org/collectionspace/services/BatchJAXBSchema.java @@ -7,7 +7,10 @@ public interface BatchJAXBSchema { final static String BATCH_NAME = "name"; final static String BATCH_NOTES = "notes"; final static String BATCH_FOR_DOC_TYPE = "forDocType"; - final static String BATCH_FOR_SINGLE_DOC = "forSingleDoc"; + final static String BATCH_SUPPORTS_SINGLE_DOC = "supportsSingleDoc"; + final static String BATCH_SUPPORTS_DOC_LIST = "supportsDocList"; + final static String BATCH_SUPPORTS_GROUP = "supportsGroup"; + // NYI final static String BATCH_SUPPORTS_SINGLE_DOC = "supportsSingleDoc"; final static String BATCH_CREATES_NEW_FOCUS = "createsNewFocus"; final static String BATCH_CLASS_NAME = "className"; } 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 3cf7b6751..55ef6ff0d 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 @@ -33,10 +33,12 @@ import org.collectionspace.services.common.ResourceBase; import org.collectionspace.services.common.ResourceMap; import org.collectionspace.services.common.ServiceMessages; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.BadRequestException; import org.collectionspace.services.common.document.DocumentHandler; import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.document.ValidatorHandler; import org.collectionspace.services.common.invocable.Invocable; +import org.collectionspace.services.common.invocable.Invocable.InvocationError; import org.collectionspace.services.common.invocable.InvocationContext; import org.collectionspace.services.common.invocable.InvocationResults; import org.jboss.resteasy.spi.ResteasyProviderFactory; @@ -48,14 +50,18 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @Path(BatchClient.SERVICE_PATH) @Produces({"application/xml"}) @Consumes({"application/xml"}) public class BatchResource extends ResourceBase { + + protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode(); @Override public String getServiceName(){ @@ -90,6 +96,23 @@ public class BatchResource extends ResourceBase { DocumentWrapper wrapper = getRepositoryClient(ctx).getDoc(ctx, csid); DocumentModel docModel = wrapper.getWrappedObject(); + String invocationMode = invContext.getMode(); + String modeProperty = null; + if(BatchInvocable.INVOCATION_MODE_SINGLE.equalsIgnoreCase(invocationMode)) { + modeProperty = BatchJAXBSchema.BATCH_SUPPORTS_SINGLE_DOC; + } else if(BatchInvocable.INVOCATION_MODE_LIST.equalsIgnoreCase(invocationMode)) { + modeProperty = BatchJAXBSchema.BATCH_SUPPORTS_DOC_LIST; + } else if(BatchInvocable.INVOCATION_MODE_GROUP.equalsIgnoreCase(invocationMode)) { + modeProperty = BatchJAXBSchema.BATCH_SUPPORTS_GROUP; + } else { + throw new BadRequestException("BatchResource: unknown Invocation Mode: " + +invocationMode); + } + Boolean supports = (Boolean)docModel.getPropertyValue(modeProperty); + if(!supports) { + throw new BadRequestException("BatchResource: This Batch Job does not support Invocation Mode: " + +invocationMode); + } String className = (String)docModel.getPropertyValue(BatchJAXBSchema.BATCH_CLASS_NAME); className = className.trim(); @@ -103,15 +126,15 @@ public class BatchResource extends ResourceBase { } else { BatchInvocable batchInstance = (BatchInvocable)c.newInstance(); List modes = batchInstance.getSupportedInvocationModes(); - if(!modes.contains(invContext.getMode())) { - throw new RuntimeException( + if(!modes.contains(invocationMode)) { + throw new BadRequestException( "BatchResource: Invoked with unsupported context mode: " - +invContext.getMode()); + +invocationMode); } String forDocType = (String)docModel.getPropertyValue(BatchJAXBSchema.BATCH_FOR_DOC_TYPE); if(!forDocType.equalsIgnoreCase(invContext.getDocType())) { - throw new RuntimeException( + throw new BadRequestException( "BatchResource: Invoked with unsupported document type: " +invContext.getDocType()); } @@ -130,9 +153,17 @@ public class BatchResource extends ResourceBase { batchInstance.run(); int status = batchInstance.getCompletionStatus(); if(status == Invocable.STATUS_ERROR) { - throw new RuntimeException( + InvocationError error = batchInstance.getErrorInfo(); + if(error.getResponseCode() == BAD_REQUEST_STATUS) { + throw new BadRequestException( "BatchResouce: batchProcess encountered error: " +batchInstance.getErrorInfo()); + } else { + throw new RuntimeException( + "BatchResouce: batchProcess encountered error: " + +batchInstance.getErrorInfo()); + + } } InvocationResults results = batchInstance.getResults(); return results; diff --git a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java index b0f77f4e3..77fdad9b6 100644 --- a/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java +++ b/services/batch/service/src/main/java/org/collectionspace/services/batch/nuxeo/CreateAndLinkLoanOutBatchJob.java @@ -4,9 +4,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import javax.ws.rs.core.Response; + import org.collectionspace.services.batch.BatchInvocable; +import org.collectionspace.services.client.CollectionSpaceClientUtils; import org.collectionspace.services.common.ResourceBase; import org.collectionspace.services.common.ResourceMap; +import org.collectionspace.services.common.datetime.GregorianCalendarDateTimeUtils; import org.collectionspace.services.common.invocable.InvocationContext; import org.collectionspace.services.common.invocable.InvocationResults; @@ -15,9 +19,15 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { private static ArrayList invocationModes = null; private InvocationContext context; private int completionStatus; - private HashMap resourceMap; + private ResourceMap resourceMap; private InvocationResults results; - private String errorInfo; + private InvocationError errorInfo; + private final String RELATION_TYPE = "affects"; + private final String LOAN_DOCTYPE = "LoanOut"; + private final String RELATION_PREDICATE_DISP = "affects"; + protected final int CREATED_STATUS = Response.Status.CREATED.getStatusCode(); + protected final int BAD_REQUEST_STATUS = Response.Status.BAD_REQUEST.getStatusCode(); + protected final int INT_ERROR_STATUS = Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); public CreateAndLinkLoanOutBatchJob() { CreateAndLinkLoanOutBatchJob.setupClassStatics(); @@ -25,13 +35,14 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { completionStatus = STATUS_UNSTARTED; resourceMap = null; results = new InvocationResults(); - errorInfo = ""; + errorInfo = null; } private static void setupClassStatics() { if(invocationModes == null ) { invocationModes = new ArrayList(1); invocationModes.add(INVOCATION_MODE_SINGLE); + invocationModes.add(INVOCATION_MODE_LIST); } } @@ -55,20 +66,108 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { * @param context an instance of InvocationContext. */ public void setResourceMap(ResourceMap resourceMap) { + this.resourceMap = resourceMap; } /** * The main work logic of the batch job. Will be called after setContext. */ public void run() { - completionStatus = STATUS_UNSTARTED; + completionStatus = STATUS_MIN_PROGRESS; + try { - Thread.sleep(1000); - } catch(Exception e) {} - results.setPrimaryURICreated(null); - results.setNumAffected(0); - results.setUserNote("CreateAndLinkLoanOutBatchJob pretended to do work, and completed"); - completionStatus = STATUS_COMPLETE; + // First, create the Loanout + if(createLoan() != STATUS_ERROR) { + if(INVOCATION_MODE_SINGLE.equalsIgnoreCase(context.getMode())) { + if(createRelation(results.getPrimaryURICreated(), + context.getSingleCSID()) != STATUS_ERROR) { + results.setNumAffected(1); + results.setUserNote("CreateAndLinkLoanOutBatchJob created new Loanout: " + +results.getPrimaryURICreated()+" with a link to the passed "+context.getDocType()); + completionStatus = STATUS_COMPLETE; + } + } else if(INVOCATION_MODE_LIST.equalsIgnoreCase(context.getMode())) { + InvocationContext.ListCSIDs listWrapper = context.getListCSIDs(); + List csids = listWrapper.getCsid(); + if(csids.size()==0) { + completionStatus = STATUS_ERROR; + errorInfo = new InvocationError(BAD_REQUEST_STATUS, + "CreateAndLinkLoanOutBatchJob: no CSIDs in list of documents!"); + results.setUserNote(errorInfo.getMessage()); + } + String loanCSID = results.getPrimaryURICreated(); + int nCreated = 0; + for(String csid:csids) { + if(createRelation(loanCSID, csid) == STATUS_ERROR) { + break; + } else { + nCreated++; + } + } + if(completionStatus!=STATUS_ERROR) { + results.setNumAffected(nCreated); + results.setUserNote("CreateAndLinkLoanOutBatchJob created new Loanout: " + +results.getPrimaryURICreated()+" with "+nCreated+" link(s) to "+context.getDocType()); + completionStatus = STATUS_COMPLETE; + } + } + } + } catch(Exception e) { + completionStatus = STATUS_ERROR; + errorInfo = new InvocationError(INT_ERROR_STATUS, + "CreateAndLinkLoanOutBatchJob problem creating new Loanout: "+e.getLocalizedMessage()); + results.setUserNote(errorInfo.getMessage()); + } + } + + private int createLoan() { + String newLoanNumber = "NewLoan-"+ GregorianCalendarDateTimeUtils.timestampUTC(); + + String loanoutPayload = "" + +"" + +"" + +""+newLoanNumber+"" + +""; + + // First, create the Loanout + ResourceBase resource = + resourceMap.get("org.collectionspace.services.loanout.LoanoutResource"); + Response response = resource.create(null, loanoutPayload); + if(response.getStatus() != CREATED_STATUS) { + completionStatus = STATUS_ERROR; + errorInfo = new InvocationError(INT_ERROR_STATUS, + "CreateAndLinkLoanOutBatchJob problem creating new Loanout!"); + results.setUserNote(errorInfo.getMessage()); + } else { + String newId = CollectionSpaceClientUtils.extractId(response); + results.setPrimaryURICreated(newId); + } + return completionStatus; + } + + private int createRelation(String loanCSID, String toCSID) { + // Now, create the relation that links the input object to the loanout + String relationPayload = "" + + "" + + ""+loanCSID+"" + + ""+LOAN_DOCTYPE+"" + + ""+toCSID+"" + + ""+context.getDocType()+"" + + ""+RELATION_TYPE+"" + + ""+RELATION_PREDICATE_DISP+"" + + ""; + ResourceBase resource = + resourceMap.get("org.collectionspace.services.relation.RelationResource"); + Response response = resource.create(null, relationPayload); + if(response.getStatus() != CREATED_STATUS) { + completionStatus = STATUS_ERROR; + errorInfo = new InvocationError(INT_ERROR_STATUS, + "CreateAndLinkLoanOutBatchJob problem creating new relation!"); + results.setUserNote(errorInfo.getMessage()); + } + return completionStatus; } /** @@ -93,7 +192,7 @@ public class CreateAndLinkLoanOutBatchJob implements BatchInvocable { * @return a user-presentable note when an error occurs in batch processing. Will only * be called if getCompletionStatus() returns STATUS_ERROR. */ - public String getErrorInfo() { + public InvocationError getErrorInfo() { return errorInfo; } diff --git a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceClientUtils.java b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceClientUtils.java index 4cd6764e7..8f2bf0b8f 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceClientUtils.java +++ b/services/client/src/main/java/org/collectionspace/services/client/CollectionSpaceClientUtils.java @@ -79,7 +79,23 @@ public class CollectionSpaceClientUtils { */ static public String extractId(ClientResponse res) { MultivaluedMap mvm = res.getMetadata(); - String uri = (String) ((List) mvm.get("Location")).get(0); + return extractIdFromResponseMetadata(mvm); + } + + /** + * Extract id. + * + * @param res the res + * @return the string + */ + static public String extractId(Response res) { + MultivaluedMap mvm = res.getMetadata(); + return extractIdFromResponseMetadata(mvm); + } + + static protected String extractIdFromResponseMetadata(MultivaluedMap mvm) { + // mvm may return a java.net.URI which complains about casting to String... + String uri = ((List) mvm.get("Location")).get(0).toString(); if (logger.isDebugEnabled()) { logger.debug("extractId:uri=" + uri); } 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 16212a812..c5c2f6669 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 @@ -34,6 +34,32 @@ import java.util.List; * $LastChangedDate: $ */ public interface Invocable { + + public class InvocationError { + int responseCode; + String message; + + public InvocationError(int responseCode, String message) { + this.responseCode = responseCode; + this.message = message; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } public String INVOCATION_MODE_SINGLE = "single"; public String INVOCATION_MODE_GROUP = "group"; @@ -77,6 +103,6 @@ public interface Invocable { * @return a user-presentable note when an error occurs in batch processing. Will only * be called if getCompletionStatus() returns STATUS_ERROR. */ - public String getErrorInfo(); + public InvocationError getErrorInfo(); } diff --git a/services/jaxb/src/main/resources/invocationContext.xsd b/services/jaxb/src/main/resources/invocationContext.xsd index 0f1c0b9bd..8c5089af9 100644 --- a/services/jaxb/src/main/resources/invocationContext.xsd +++ b/services/jaxb/src/main/resources/invocationContext.xsd @@ -26,7 +26,7 @@ - + -- 2.47.3