<?xml version="1.0" encoding="UTF-8"?>\r
<xmlReplay>\r
- <auths>\r
- <!-- IMPORTANT: THESE ARE STICKY :: THEY STICK AROUND UNTIL RESET, IN EXEC ORDER OF THIS FILE. -->\r
- <auth ID="admin@collectionspace.org">YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y</auth>\r
- <auth ID="testAdministator">YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y</auth>\r
- </auths>\r
- \r
- <testGroup ID="primary" autoDeletePOSTS="false">\r
- <test ID="ba1" auth="test">\r
- <method>POST</method>\r
- <uri>/cspace-services/batch/</uri>\r
- <filename>batch/batch1.xml</filename>\r
- </test>\r
- <test ID="ba2" auth="test">\r
- <method>POST</method>\r
- <uri>/cspace-services/collectionobjects/</uri>\r
- <filename>batch/collObj1.xml</filename>\r
- </test>\r
- <test ID="ba3" auth="test">\r
- <method>POST</method>\r
- <uri>/cspace-services/batch/${ba1.CSID}</uri>\r
- <filename>batch/batch1InvContext.xml</filename>\r
- <vars>\r
- <var ID="CollObj1">${ba2.CSID}</var>\r
- </vars>\r
- </test>\r
- </testGroup> \r
-\r
+ <auths>\r
+ <!-- IMPORTANT: THESE ARE STICKY :: THEY STICK AROUND UNTIL RESET, IN EXEC ORDER OF THIS FILE. -->\r
+ <auth ID="admin@collectionspace.org">YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y</auth>\r
+ <auth ID="testAdministator">YWRtaW5AY29sbGVjdGlvbnNwYWNlLm9yZzpBZG1pbmlzdHJhdG9y</auth>\r
+ </auths>\r
+ <testGroup ID="testSingle" autoDeletePOSTS="true">\r
+ <test ID="createCollObj1" auth="test">\r
+ <method>POST</method>\r
+ <uri>/cspace-services/batch/</uri>\r
+ <filename>batch/batch1.xml</filename>\r
+ </test>\r
+ <test ID="createCollObj2" auth="test">\r
+ <method>POST</method>\r
+ <uri>/cspace-services/batch/</uri>\r
+ <filename>batch/batch1.xml</filename>\r
+ </test>\r
+ <test ID="createBatch" auth="test">\r
+ <method>POST</method>\r
+ <uri>/cspace-services/collectionobjects/</uri>\r
+ <filename>batch/collObj1.xml</filename>\r
+ </test>\r
+ <test ID="testInvokeSingle" auth="test" autoDeletePOSTS="false">\r
+ <method>POST</method>\r
+ <uri>/cspace-services/batch/${createCollObj1.CSID}</uri>\r
+ <filename>batch/batchInvContextSingle.xml</filename>\r
+ <vars>\r
+ <var ID="CollObj1">${createBatch.CSID}</var>\r
+ </vars>\r
+ </test>\r
+ <test ID="testInvokeList" auth="test" autoDeletePOSTS="false">\r
+ <method>POST</method>\r
+ <uri>/cspace-services/batch/${createCollObj1.CSID}</uri>\r
+ <filename>batch/batchInvContextList.xml</filename>\r
+ <vars>\r
+ <var ID="CollObj1">${createBatch.CSID}</var>\r
+ </vars>\r
+ </test>\r
+ <test ID="testBadInvoke" auth="test" autoDeletePOSTS="false">\r
+ <method>POST</method>\r
+ <expectedCodes>400</expectedCodes>\r
+ <uri>/cspace-services/batch/${createCollObj1.CSID}</uri>\r
+ <filename>batch/batchBadInvContext.xml</filename>\r
+ <vars>\r
+ <var ID="CollObj1">${createBatch.CSID}</var>\r
+ </vars>\r
+ </test>\r
+ <test ID="testInvokeEmptyList" auth="test" autoDeletePOSTS="false">\r
+ <method>POST</method>\r
+ <expectedCodes>400</expectedCodes>\r
+ <uri>/cspace-services/batch/${createCollObj1.CSID}</uri>\r
+ <filename>batch/batchBadInvContextList.xml</filename>\r
+ <vars>\r
+ <var ID="CollObj1">${createBatch.CSID}</var>\r
+ </vars>\r
+ </test>\r
+ </testGroup>\r
+ <!-- \r
<testGroup ID="cleanup" autoDeletePOSTS="true">\r
<test ID="cl1" auth="test">\r
<method>DELETE</method>\r
- <uri>/cspace-services/batch/${ba1.CSID}</uri>\r
+ <uri>/cspace-services/batch/${createCollObj1.CSID}</uri>\r
</test>\r
<test ID="cl2" auth="test">\r
<method>DELETE</method>\r
- <uri>/cspace-services/collectionobjects/${ba2.CSID}</uri>\r
+ <uri>/cspace-services/collectionobjects/${createBatch.CSID}</uri>\r
</test>\r
</testGroup>\r
- \r
-\r
+ -->\r
</xmlReplay>\r
+\r
<name>TestCreateAndLinkLoanOutBatchJob</name>\r
<notes>This should be interesting</notes>\r
<forDocType>CollectionObject</forDocType>\r
- <forSingleDoc>true</forSingleDoc>\r
+ <supportsSingleDoc>true</supportsSingleDoc>\r
+ <supportsDocList>true</supportsDocList>\r
<createsNewFocus>true</createsNewFocus>\r
<className>org.collectionspace.services.batch.nuxeo.CreateAndLinkLoanOutBatchJob</className>\r
</ns2:batch_common>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
+<ns2:invocationContext\r
+xmlns:ns2="http://collectionspace.org/services/common/invocable"\r
+xmlns:ns3="http://collectionspace.org/services/jaxb">\r
+ <mode>garbage</mode>\r
+ <docType>CollectionObject</docType>\r
+ <listCSIDs>\r
+ <csids>${CollObj1}</csids>\r
+ </listCSIDs>\r
+</ns2:invocationContext>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
+<ns2:invocationContext\r
+xmlns:ns2="http://collectionspace.org/services/common/invocable"\r
+xmlns:ns3="http://collectionspace.org/services/jaxb">\r
+ <mode>list</mode>\r
+ <docType>CollectionObject</docType>\r
+ <listCSIDs>\r
+ </listCSIDs>\r
+</ns2:invocationContext>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
+<ns2:invocationContext\r
+xmlns:ns2="http://collectionspace.org/services/common/invocable"\r
+xmlns:ns3="http://collectionspace.org/services/jaxb">\r
+ <mode>list</mode>\r
+ <docType>CollectionObject</docType>\r
+ <listCSIDs>\r
+ <csid>${CollObj1}</csid>\r
+ </listCSIDs>\r
+</ns2:invocationContext>\r
+\r
+\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
+<ns2:invocationContext\r
+xmlns:ns2="http://collectionspace.org/services/common/invocable"\r
+xmlns:ns3="http://collectionspace.org/services/jaxb">\r
+ <mode>single</mode>\r
+ <docType>CollectionObject</docType>\r
+ <singleCSID>${CollObj1}</singleCSID>\r
+</ns2:invocationContext>\r
+\r
+\r
<run controlFile="person/person.xml" testGroup="PersonAddRelsDeleteRels" />\r
<run controlFile="location/location-hierarchy.xml" testGroup="HierarchicLocation" />\r
<run controlFile="organization/organization-hierarchy.xml" testGroup="HierarchicOrganization" />\r
+ <run controlFile="batch/batch.xml" />\r
\r
\r
\r
<xs:element name="name" type="xs:string"/>\r
<xs:element name="notes" type="xs:string"/>\r
<xs:element name="forDocType" type="xs:string"/>\r
- <xs:element name="forSingleDoc" type="xs:boolean"/>\r
+ <xs:element name="supportsSingleDoc" type="xs:boolean"/>\r
+ <xs:element name="supportsDocList" type="xs:boolean"/>\r
+ <xs:element name="supportsGroup" type="xs:boolean"/>\r
+ <!-- NYI <xs:element name="supportsQuery" type="xs:boolean"/> -->\r
<xs:element name="createsNewFocus" type="xs:boolean"/>\r
<xs:element name="className" type="xs:string"/>\r
</xs:schema>\r
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";
}
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;
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(){
DocumentWrapper<DocumentModel> 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();
} else {
BatchInvocable batchInstance = (BatchInvocable)c.newInstance();
List<String> 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());
}
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;
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;
private static ArrayList<String> invocationModes = null;
private InvocationContext context;
private int completionStatus;
- private HashMap<String,ResourceBase> 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();
completionStatus = STATUS_UNSTARTED;
resourceMap = null;
results = new InvocationResults();
- errorInfo = "";
+ errorInfo = null;
}
private static void setupClassStatics() {
if(invocationModes == null ) {
invocationModes = new ArrayList<String>(1);
invocationModes.add(INVOCATION_MODE_SINGLE);
+ invocationModes.add(INVOCATION_MODE_LIST);
}
}
* @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<String> 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ +"<document name=\"loansout\">"
+ +"<ns2:loansout_common xmlns:ns2=\"http://collectionspace.org/services/loanout\""
+ +" xmlns:ns3=\"http://collectionspace.org/services/jaxb\">"
+ +"<loanOutNumber>"+newLoanNumber+"</loanOutNumber>"
+ +"</ns2:loansout_common></document>";
+
+ // 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 = "<document name=\"relations\">"
+ + "<ns2:relations_common xmlns:ns2=\"http://collectionspace.org/services/relation\""
+ + " xmlns:ns3=\"http://collectionspace.org/services/jaxb\">"
+ + "<documentId1>"+loanCSID+"</documentId1>"
+ + "<documentType1>"+LOAN_DOCTYPE+"</documentType1>"
+ + "<documentId2>"+toCSID+"</documentId2>"
+ + "<documentType2>"+context.getDocType()+"</documentType2>"
+ + "<relationshipType>"+RELATION_TYPE+"</relationshipType>"
+ + "<predicateDisplayName>"+RELATION_PREDICATE_DISP+"</predicateDisplayName>"
+ + "</ns2:relations_common></document>";
+ 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;
}
/**
* @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;
}
*/\r
static public String extractId(ClientResponse<Response> res) {\r
MultivaluedMap<String, Object> mvm = res.getMetadata();\r
- String uri = (String) ((List<Object>) mvm.get("Location")).get(0);\r
+ return extractIdFromResponseMetadata(mvm);\r
+ }\r
+ \r
+ /**\r
+ * Extract id.\r
+ *\r
+ * @param res the res\r
+ * @return the string\r
+ */\r
+ static public String extractId(Response res) {\r
+ MultivaluedMap<String, Object> mvm = res.getMetadata();\r
+ return extractIdFromResponseMetadata(mvm);\r
+ }\r
+ \r
+ static protected String extractIdFromResponseMetadata(MultivaluedMap<String, Object> mvm) {\r
+ // mvm may return a java.net.URI which complains about casting to String...\r
+ String uri = ((List<Object>) mvm.get("Location")).get(0).toString();\r
if (logger.isDebugEnabled()) {\r
logger.debug("extractId:uri=" + uri);\r
}\r
* $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";
* @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();
}
<xs:element name="listCSIDs">
<xs:complexType>
<xs:sequence>
- <xs:element name="csids" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:element name="csid" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>