3947 support http delete for operation $export poll status (#3961)
* Added failing test, and the methods that are necessary to not break the build * Implemented solution to cancel jobs via delete request for poll status * change log added * change log name change to pass test * Changed implementation to work with existing infrastructure * code review changes Co-authored-by: Steven Li <steven@smilecdr.com>
This commit is contained in:
parent
bd39d65d0e
commit
65d4fe81ad
|
@ -25,7 +25,7 @@ public final class Msg {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IMPORTANT: Please update the following comment after you add a new code
|
* IMPORTANT: Please update the following comment after you add a new code
|
||||||
* Last code value: 2130
|
* Last code value: 2131
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private Msg() {}
|
private Msg() {}
|
||||||
|
|
|
@ -91,6 +91,17 @@ public @interface Operation {
|
||||||
*/
|
*/
|
||||||
boolean idempotent() default false;
|
boolean idempotent() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To support cancelling of a job,
|
||||||
|
* this flag should be set to <code>true</code> (default is <code>false</code>).
|
||||||
|
* <p>
|
||||||
|
* The server, when setting this to <code>true</code>,
|
||||||
|
* will allow the operation to be invoked using an <code>HTTP DELETE</code>
|
||||||
|
* (on top of the standard <code>HTTP POST</code>)
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
boolean deleteEnabled() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This parameter may be used to specify the parts which will be found in the
|
* This parameter may be used to specify the parts which will be found in the
|
||||||
* response to this operation.
|
* response to this operation.
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 3947
|
||||||
|
title: "Previously, DELETE request type is not supported for any operations. DELETE is now supported, and is enabled for operation $export-poll-status to allow cancellation of jobs"
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.bulk;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.Batch2JobOperationResult;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
||||||
|
@ -21,6 +22,7 @@ import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
@ -65,6 +67,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -195,7 +198,7 @@ public class BulkDataExportProviderTest {
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters params = verifyJobStart();;
|
BulkExportParameters params = verifyJobStart();
|
||||||
assertEquals(Constants.CT_FHIR_NDJSON, params.getOutputFormat());
|
assertEquals(Constants.CT_FHIR_NDJSON, params.getOutputFormat());
|
||||||
assertThat(params.getResourceTypes(), containsInAnyOrder("Patient", "Practitioner"));
|
assertThat(params.getResourceTypes(), containsInAnyOrder("Patient", "Practitioner"));
|
||||||
assertThat(params.getStartDate(), notNullValue());
|
assertThat(params.getStartDate(), notNullValue());
|
||||||
|
@ -224,7 +227,7 @@ public class BulkDataExportProviderTest {
|
||||||
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
assertEquals("http://localhost:" + myPort + "/$export-poll-status?_jobId=" + A_JOB_ID, response.getFirstHeader(Constants.HEADER_CONTENT_LOCATION).getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BulkExportParameters params = verifyJobStart();;
|
BulkExportParameters params = verifyJobStart();
|
||||||
assertEquals(Constants.CT_FHIR_NDJSON, params.getOutputFormat());
|
assertEquals(Constants.CT_FHIR_NDJSON, params.getOutputFormat());
|
||||||
assertThat(params.getResourceTypes(), containsInAnyOrder("Patient", "EpisodeOfCare"));
|
assertThat(params.getResourceTypes(), containsInAnyOrder("Patient", "EpisodeOfCare"));
|
||||||
assertThat(params.getStartDate(), nullValue());
|
assertThat(params.getStartDate(), nullValue());
|
||||||
|
@ -454,12 +457,12 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
InstantType now = InstantType.now();
|
InstantType now = InstantType.now();
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT
|
String url = "http://localhost:" + myPort + "/" + GROUP_ID + "/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("Patient, Practitioner")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
+ "&" + JpaConstants.PARAM_EXPORT_SINCE + "=" + UrlUtil.escapeUrlParam(now.getValueAsString())
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE_FILTER + "=" + UrlUtil.escapeUrlParam("Patient?identifier=foo|bar")
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE_FILTER + "=" + UrlUtil.escapeUrlParam("Patient?identifier=foo|bar")
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_MDM+ "=true";
|
+ "&" + JpaConstants.PARAM_EXPORT_MDM + "=true";
|
||||||
|
|
||||||
// call
|
// call
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
|
@ -522,7 +525,7 @@ public class BulkDataExportProviderTest {
|
||||||
public void testInitiateGroupExportWithInvalidResourceTypesFails() throws IOException {
|
public void testInitiateGroupExportWithInvalidResourceTypesFails() throws IOException {
|
||||||
// when
|
// when
|
||||||
|
|
||||||
String url = "http://localhost:" + myPort + "/" + "Group/123/" +JpaConstants.OPERATION_EXPORT
|
String url = "http://localhost:" + myPort + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON)
|
||||||
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("StructureDefinition,Observation");
|
+ "&" + JpaConstants.PARAM_EXPORT_TYPE + "=" + UrlUtil.escapeUrlParam("StructureDefinition,Observation");
|
||||||
|
|
||||||
|
@ -546,7 +549,7 @@ public class BulkDataExportProviderTest {
|
||||||
String url = "http://localhost:" + myPort + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
String url = "http://localhost:" + myPort + "/" + "Group/123/" + JpaConstants.OPERATION_EXPORT
|
||||||
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON);
|
+ "?" + JpaConstants.PARAM_EXPORT_OUTPUT_FORMAT + "=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_NDJSON);
|
||||||
|
|
||||||
HttpGet get = new HttpGet(url);
|
HttpGet get = new HttpGet(url);
|
||||||
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
get.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC);
|
||||||
CloseableHttpResponse execute = myClient.execute(get);
|
CloseableHttpResponse execute = myClient.execute(get);
|
||||||
|
|
||||||
|
@ -676,6 +679,71 @@ public class BulkDataExportProviderTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteForOperationPollStatus_SUBMITTED_ShouldCancelJobSuccessfully() throws IOException {
|
||||||
|
// setup
|
||||||
|
Batch2JobInfo info = new Batch2JobInfo();
|
||||||
|
info.setJobId(A_JOB_ID);
|
||||||
|
info.setStatus(BulkExportJobStatusEnum.SUBMITTED);
|
||||||
|
info.setEndTime(InstantType.now().getValue());
|
||||||
|
Batch2JobOperationResult result = new Batch2JobOperationResult();
|
||||||
|
result.setOperation("Cancel job instance " + A_JOB_ID);
|
||||||
|
result.setMessage("Job instance <" + A_JOB_ID + "> successfully cancelled.");
|
||||||
|
result.setSuccess(true);
|
||||||
|
|
||||||
|
// when
|
||||||
|
when(myJobRunner.getJobInfo(eq(A_JOB_ID)))
|
||||||
|
.thenReturn(info);
|
||||||
|
when(myJobRunner.cancelInstance(eq(A_JOB_ID)))
|
||||||
|
.thenReturn(result);
|
||||||
|
|
||||||
|
// call
|
||||||
|
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
|
HttpDelete delete = new HttpDelete(url);
|
||||||
|
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
||||||
|
ourLog.info("Response: {}", response.toString());
|
||||||
|
|
||||||
|
assertEquals(202, response.getStatusLine().getStatusCode());
|
||||||
|
assertEquals("Accepted", response.getStatusLine().getReasonPhrase());
|
||||||
|
|
||||||
|
verify(myJobRunner, times(1)).cancelInstance(A_JOB_ID);
|
||||||
|
String responseContent = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||||
|
ourLog.info("Response content: {}", responseContent);
|
||||||
|
assertThat(responseContent, containsString("successfully cancelled."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteForOperationPollStatus_COMPLETE_ShouldReturnError() throws IOException {
|
||||||
|
// setup
|
||||||
|
Batch2JobInfo info = new Batch2JobInfo();
|
||||||
|
info.setJobId(A_JOB_ID);
|
||||||
|
info.setStatus(BulkExportJobStatusEnum.COMPLETE);
|
||||||
|
info.setEndTime(InstantType.now().getValue());
|
||||||
|
|
||||||
|
// when
|
||||||
|
when(myJobRunner.getJobInfo(eq(A_JOB_ID)))
|
||||||
|
.thenReturn(info);
|
||||||
|
|
||||||
|
// call
|
||||||
|
String url = "http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT_POLL_STATUS + "?" +
|
||||||
|
JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID + "=" + A_JOB_ID;
|
||||||
|
HttpDelete delete = new HttpDelete(url);
|
||||||
|
try (CloseableHttpResponse response = myClient.execute(delete)) {
|
||||||
|
ourLog.info("Response: {}", response.toString());
|
||||||
|
|
||||||
|
assertEquals(404, response.getStatusLine().getStatusCode());
|
||||||
|
assertEquals("Not Found", response.getStatusLine().getReasonPhrase());
|
||||||
|
|
||||||
|
verify(myJobRunner, times(1)).cancelInstance(A_JOB_ID);
|
||||||
|
String responseContent = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||||
|
// content would be blank, since the job is cancelled, so no
|
||||||
|
ourLog.info("Response content: {}", responseContent);
|
||||||
|
assertThat(responseContent, containsString("was already cancelled or has completed."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void callExportAndAssertJobId(Parameters input, String theExpectedJobId) throws IOException {
|
private void callExportAndAssertJobId(Parameters input, String theExpectedJobId) throws IOException {
|
||||||
HttpPost post;
|
HttpPost post;
|
||||||
post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
post = new HttpPost("http://localhost:" + myPort + "/" + JpaConstants.OPERATION_EXPORT);
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class GraphQLMethodBinding extends OperationMethodBinding {
|
||||||
private final RequestTypeEnum myMethodRequestType;
|
private final RequestTypeEnum myMethodRequestType;
|
||||||
|
|
||||||
public GraphQLMethodBinding(Method theMethod, RequestTypeEnum theMethodRequestType, FhirContext theContext, Object theProvider) {
|
public GraphQLMethodBinding(Method theMethod, RequestTypeEnum theMethodRequestType, FhirContext theContext, Object theProvider) {
|
||||||
super(null, null, theMethod, theContext, theProvider, true, Constants.OPERATION_NAME_GRAPHQL, null, null, null, null, true);
|
super(null, null, theMethod, theContext, theProvider, true, false, Constants.OPERATION_NAME_GRAPHQL, null, null, null, null, true);
|
||||||
|
|
||||||
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, theContext);
|
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, theContext);
|
||||||
myQueryUrlParamIndex = ParameterUtil.findParamAnnotationIndex(theMethod, GraphQLQueryUrl.class);
|
myQueryUrlParamIndex = ParameterUtil.findParamAnnotationIndex(theMethod, GraphQLQueryUrl.class);
|
||||||
|
|
|
@ -20,9 +20,9 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
@ -53,6 +53,7 @@ import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -61,6 +62,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
public static final String WILDCARD_NAME = "$" + Operation.NAME_MATCH_ALL;
|
public static final String WILDCARD_NAME = "$" + Operation.NAME_MATCH_ALL;
|
||||||
private final boolean myIdempotent;
|
private final boolean myIdempotent;
|
||||||
|
private final boolean myDeleteEnabled;
|
||||||
private final Integer myIdParamIndex;
|
private final Integer myIdParamIndex;
|
||||||
private final String myName;
|
private final String myName;
|
||||||
private final RestOperationTypeEnum myOtherOperationType;
|
private final RestOperationTypeEnum myOtherOperationType;
|
||||||
|
@ -82,7 +84,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
*/
|
*/
|
||||||
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
Operation theAnnotation) {
|
Operation theAnnotation) {
|
||||||
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.typeName(), theAnnotation.returnParameters(),
|
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.deleteEnabled(), theAnnotation.name(), theAnnotation.type(), theAnnotation.typeName(), theAnnotation.returnParameters(),
|
||||||
theAnnotation.bundleType(), theAnnotation.global());
|
theAnnotation.bundleType(), theAnnotation.global());
|
||||||
|
|
||||||
myManualRequestMode = theAnnotation.manualRequest();
|
myManualRequestMode = theAnnotation.manualRequest();
|
||||||
|
@ -90,12 +92,13 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType, String theOperationTypeName,
|
boolean theIdempotent, boolean theDeleteEnabled, String theOperationName, Class<? extends IBaseResource> theOperationType, String theOperationTypeName,
|
||||||
OperationParam[] theReturnParams, BundleTypeEnum theBundleType, boolean theGlobal) {
|
OperationParam[] theReturnParams, BundleTypeEnum theBundleType, boolean theGlobal) {
|
||||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
|
|
||||||
myBundleType = theBundleType;
|
myBundleType = theBundleType;
|
||||||
myIdempotent = theIdempotent;
|
myIdempotent = theIdempotent;
|
||||||
|
myDeleteEnabled = theDeleteEnabled;
|
||||||
myDescription = ParametersUtil.extractDescription(theMethod);
|
myDescription = ParametersUtil.extractDescription(theMethod);
|
||||||
myShortDescription = ParametersUtil.extractShortDefinition(theMethod);
|
myShortDescription = ParametersUtil.extractShortDefinition(theMethod);
|
||||||
myGlobal = theGlobal;
|
myGlobal = theGlobal;
|
||||||
|
@ -256,8 +259,8 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestTypeEnum requestType = theRequest.getRequestType();
|
RequestTypeEnum requestType = theRequest.getRequestType();
|
||||||
if (requestType != RequestTypeEnum.GET && requestType != RequestTypeEnum.POST) {
|
if (requestType != RequestTypeEnum.GET && requestType != RequestTypeEnum.POST && requestType != RequestTypeEnum.DELETE) {
|
||||||
// Operations can only be invoked with GET and POST
|
// Operations can only be invoked with GET, POST and DELETE
|
||||||
return MethodMatchEnum.NONE;
|
return MethodMatchEnum.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,20 +315,27 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
|
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
|
||||||
|
List<RequestTypeEnum> allowedRequestTypes = new ArrayList<>(List.of(RequestTypeEnum.POST));
|
||||||
|
if (myIdempotent) {
|
||||||
|
allowedRequestTypes.add(RequestTypeEnum.GET);
|
||||||
|
}
|
||||||
|
if (myDeleteEnabled) {
|
||||||
|
allowedRequestTypes.add(RequestTypeEnum.DELETE);
|
||||||
|
}
|
||||||
|
String messageParameter = allowedRequestTypes.stream().map(RequestTypeEnum::name).collect(Collectors.joining(", "));
|
||||||
|
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), messageParameter);
|
||||||
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
|
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
|
||||||
// all good
|
// all good
|
||||||
} else if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
} else if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
||||||
if (!myIdempotent) {
|
if (!myIdempotent) {
|
||||||
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.POST.name());
|
throw new MethodNotAllowedException(Msg.code(426) + message, allowedRequestTypes.toArray(RequestTypeEnum[]::new));
|
||||||
throw new MethodNotAllowedException(Msg.code(426) + message, RequestTypeEnum.POST);
|
}
|
||||||
|
} else if (theRequest.getRequestType() == RequestTypeEnum.DELETE) {
|
||||||
|
if (!myDeleteEnabled) {
|
||||||
|
throw new MethodNotAllowedException(Msg.code(427) + message, allowedRequestTypes.toArray(RequestTypeEnum[]::new));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!myIdempotent) {
|
throw new MethodNotAllowedException(Msg.code(428) + message, allowedRequestTypes.toArray(RequestTypeEnum[]::new));
|
||||||
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.POST.name());
|
|
||||||
throw new MethodNotAllowedException(Msg.code(427) + message, RequestTypeEnum.POST);
|
|
||||||
}
|
|
||||||
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.GET.name(), RequestTypeEnum.POST.name());
|
|
||||||
throw new MethodNotAllowedException(Msg.code(428) + message, RequestTypeEnum.GET, RequestTypeEnum.POST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myIdParamIndex != null) {
|
if (myIdParamIndex != null) {
|
||||||
|
@ -357,8 +367,13 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return myIdempotent;
|
return myIdempotent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDeleteEnabled() {
|
||||||
|
return myDeleteEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
|
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails
|
||||||
|
theDetails, Object[] theMethodParams) {
|
||||||
super.populateActionRequestDetailsForInterceptor(theRequestDetails, theDetails, theMethodParams);
|
super.populateActionRequestDetailsForInterceptor(theRequestDetails, theDetails, theMethodParams);
|
||||||
IBaseResource resource = (IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY);
|
IBaseResource resource = (IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY);
|
||||||
theRequestDetails.setResource(resource);
|
theRequestDetails.setResource(resource);
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
package ca.uhn.fhir.rest.server.method;
|
package ca.uhn.fhir.rest.server.method;
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||||
import ca.uhn.fhir.context.*;
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||||
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.context.IRuntimeDatatypeDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||||
import ca.uhn.fhir.model.api.IDatatype;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
@ -23,11 +31,20 @@ import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
import ca.uhn.fhir.util.ReflectionUtil;
|
import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.*;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -239,7 +256,7 @@ public class OperationParameter implements IParameter {
|
||||||
|
|
||||||
OperationMethodBinding method = (OperationMethodBinding) theMethodBinding;
|
OperationMethodBinding method = (OperationMethodBinding) theMethodBinding;
|
||||||
|
|
||||||
if (theRequest.getRequestType() == RequestTypeEnum.GET || method.isManualRequestMode()) {
|
if (theRequest.getRequestType() == RequestTypeEnum.GET || method.isManualRequestMode() || method.isDeleteEnabled()) {
|
||||||
translateQueryParametersIntoServerArgumentForGet(theRequest, matchingParamValues);
|
translateQueryParametersIntoServerArgumentForGet(theRequest, matchingParamValues);
|
||||||
} else {
|
} else {
|
||||||
translateQueryParametersIntoServerArgumentForPost(theRequest, matchingParamValues);
|
translateQueryParametersIntoServerArgumentForPost(theRequest, matchingParamValues);
|
||||||
|
|
|
@ -20,26 +20,25 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.util.ParametersUtil;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Validate;
|
import ca.uhn.fhir.rest.annotation.Validate;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.util.ParametersUtil;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding {
|
public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding {
|
||||||
|
|
||||||
public ValidateMethodBindingDstu2Plus(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
public ValidateMethodBindingDstu2Plus(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
Validate theAnnotation) {
|
Validate theAnnotation) {
|
||||||
super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type(), null, new OperationParam[0], BundleTypeEnum.COLLECTION, false);
|
super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, false, Constants.EXTOP_VALIDATE, theAnnotation.type(), null, new OperationParam[0], BundleTypeEnum.COLLECTION, false);
|
||||||
|
|
||||||
List<IParameter> newParams = new ArrayList<>();
|
List<IParameter> newParams = new ArrayList<>();
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
|
|
@ -21,12 +21,14 @@ package ca.uhn.fhir.batch2.jobs.services;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
||||||
|
import ca.uhn.fhir.batch2.api.JobOperationResultJson;
|
||||||
import ca.uhn.fhir.batch2.jobs.export.models.BulkExportJobParameters;
|
import ca.uhn.fhir.batch2.jobs.export.models.BulkExportJobParameters;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstance;
|
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
||||||
import ca.uhn.fhir.batch2.model.StatusEnum;
|
import ca.uhn.fhir.batch2.model.StatusEnum;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.Batch2JobOperationResult;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
||||||
import ca.uhn.fhir.jpa.batch.models.Batch2BaseJobParameters;
|
import ca.uhn.fhir.jpa.batch.models.Batch2BaseJobParameters;
|
||||||
|
@ -75,6 +77,23 @@ public class Batch2JobRunnerImpl implements IBatch2JobRunner {
|
||||||
return fromJobInstanceToBatch2JobInfo(instance);
|
return fromJobInstanceToBatch2JobInfo(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Batch2JobOperationResult cancelInstance(String theJobId) throws ResourceNotFoundException {
|
||||||
|
JobOperationResultJson cancelResult = myJobCoordinator.cancelInstance(theJobId);
|
||||||
|
if (cancelResult == null) {
|
||||||
|
throw new ResourceNotFoundException(Msg.code(2131) + " : " + theJobId);
|
||||||
|
}
|
||||||
|
return fromJobOperationResultToBatch2JobOperationResult(cancelResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Batch2JobOperationResult fromJobOperationResultToBatch2JobOperationResult(@Nonnull JobOperationResultJson theResultJson) {
|
||||||
|
Batch2JobOperationResult result = new Batch2JobOperationResult();
|
||||||
|
result.setOperation(theResultJson.getOperation());
|
||||||
|
result.setMessage(theResultJson.getMessage());
|
||||||
|
result.setSuccess(theResultJson.getSuccess());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Batch2JobInfo fromJobInstanceToBatch2JobInfo(@Nonnull JobInstance theInstance) {
|
private Batch2JobInfo fromJobInstanceToBatch2JobInfo(@Nonnull JobInstance theInstance) {
|
||||||
Batch2JobInfo info = new Batch2JobInfo();
|
Batch2JobInfo info = new Batch2JobInfo();
|
||||||
info.setJobId(theInstance.getInstanceId());
|
info.setJobId(theInstance.getInstanceId());
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package ca.uhn.fhir.jpa.api.model;
|
||||||
|
|
||||||
|
public class Batch2JobOperationResult {
|
||||||
|
// operation name
|
||||||
|
private String myOperation;
|
||||||
|
// if the operation is successful
|
||||||
|
private Boolean mySuccess;
|
||||||
|
// message of the operation
|
||||||
|
private String myMessage;
|
||||||
|
|
||||||
|
public String getOperation() {
|
||||||
|
return myOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperation(String theOperation) {
|
||||||
|
myOperation = theOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getSuccess() {
|
||||||
|
return mySuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(Boolean theSuccess) {
|
||||||
|
mySuccess = theSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return myMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String theMessage) {
|
||||||
|
myMessage = theMessage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.api.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.Batch2JobOperationResult;
|
||||||
import ca.uhn.fhir.jpa.batch.models.Batch2BaseJobParameters;
|
import ca.uhn.fhir.jpa.batch.models.Batch2BaseJobParameters;
|
||||||
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
|
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
|
||||||
|
|
||||||
|
@ -29,14 +30,20 @@ public interface IBatch2JobRunner {
|
||||||
/**
|
/**
|
||||||
* Start the job with the given parameters
|
* Start the job with the given parameters
|
||||||
* @param theParameters
|
* @param theParameters
|
||||||
* @return returns the job id
|
* @return returns the job id
|
||||||
*/
|
*/
|
||||||
Batch2JobStartResponse startNewJob(Batch2BaseJobParameters theParameters);
|
Batch2JobStartResponse startNewJob(Batch2BaseJobParameters theParameters);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns information about a provided job.
|
* Returns information about a provided job.
|
||||||
|
*
|
||||||
* @param theJobId - the job id
|
* @param theJobId - the job id
|
||||||
* @return - the batch2 job info
|
* @return - the batch2 job info
|
||||||
*/
|
*/
|
||||||
Batch2JobInfo getJobInfo(String theJobId);
|
Batch2JobInfo getJobInfo(String theJobId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the job provided
|
||||||
|
*/
|
||||||
|
Batch2JobOperationResult cancelInstance(String theJobId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
import ca.uhn.fhir.jpa.api.model.Batch2JobInfo;
|
||||||
|
import ca.uhn.fhir.jpa.api.model.Batch2JobOperationResult;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
import ca.uhn.fhir.jpa.api.model.BulkExportJobResults;
|
||||||
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
import ca.uhn.fhir.jpa.api.model.BulkExportParameters;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner;
|
||||||
|
@ -42,6 +43,7 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.PreferHeader;
|
import ca.uhn.fhir.rest.api.PreferHeader;
|
||||||
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
import ca.uhn.fhir.rest.api.server.bulk.BulkDataExportOptions;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
@ -149,8 +151,7 @@ public class BulkDataExportProvider {
|
||||||
private String getDefaultPartitionServerBase(ServletRequestDetails theRequestDetails) {
|
private String getDefaultPartitionServerBase(ServletRequestDetails theRequestDetails) {
|
||||||
if (theRequestDetails.getTenantId() == null || theRequestDetails.getTenantId().equals(JpaConstants.DEFAULT_PARTITION_NAME)) {
|
if (theRequestDetails.getTenantId() == null || theRequestDetails.getTenantId().equals(JpaConstants.DEFAULT_PARTITION_NAME)) {
|
||||||
return getServerBase(theRequestDetails);
|
return getServerBase(theRequestDetails);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return StringUtils.removeEnd(theRequestDetails.getServerBaseForRequest().replace(theRequestDetails.getTenantId(), JpaConstants.DEFAULT_PARTITION_NAME), "/");
|
return StringUtils.removeEnd(theRequestDetails.getServerBaseForRequest().replace(theRequestDetails.getTenantId(), JpaConstants.DEFAULT_PARTITION_NAME), "/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,52 +238,55 @@ public class BulkDataExportProvider {
|
||||||
/**
|
/**
|
||||||
* $export-poll-status
|
* $export-poll-status
|
||||||
*/
|
*/
|
||||||
@Operation(name = JpaConstants.OPERATION_EXPORT_POLL_STATUS, manualResponse = true, idempotent = true)
|
@Operation(name = JpaConstants.OPERATION_EXPORT_POLL_STATUS, manualResponse = true, idempotent = true, deleteEnabled = true)
|
||||||
public void exportPollStatus(
|
public void exportPollStatus(
|
||||||
@OperationParam(name = JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, typeName = "string", min = 0, max = 1) IPrimitiveType<String> theJobId,
|
@OperationParam(name = JpaConstants.PARAM_EXPORT_POLL_STATUS_JOB_ID, typeName = "string", min = 0, max = 1) IPrimitiveType<String> theJobId,
|
||||||
ServletRequestDetails theRequestDetails
|
ServletRequestDetails theRequestDetails
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
HttpServletResponse response = theRequestDetails.getServletResponse();
|
HttpServletResponse response = theRequestDetails.getServletResponse();
|
||||||
theRequestDetails.getServer().addHeadersToResponse(response);
|
theRequestDetails.getServer().addHeadersToResponse(response);
|
||||||
|
|
||||||
Batch2JobInfo info = myJobRunner.getJobInfo(theJobId.getValueAsString());
|
Batch2JobInfo info = myJobRunner.getJobInfo(theJobId.getValueAsString());
|
||||||
|
|
||||||
switch (info.getStatus()) {
|
switch (info.getStatus()) {
|
||||||
case COMPLETE:
|
case COMPLETE:
|
||||||
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
if (theRequestDetails.getRequestType() == RequestTypeEnum.DELETE) {
|
||||||
response.setContentType(Constants.CT_JSON);
|
handleDeleteRequest(theJobId, response, info.getStatus());
|
||||||
|
|
||||||
// Create a JSON response
|
|
||||||
BulkExportResponseJson bulkResponseDocument = new BulkExportResponseJson();
|
|
||||||
bulkResponseDocument.setTransactionTime(info.getEndTime()); // completed
|
|
||||||
|
|
||||||
String report = info.getReport();
|
|
||||||
if (isEmpty(report)) {
|
|
||||||
// this should never happen, but just in case...
|
|
||||||
ourLog.error("No report for completed bulk export job.");
|
|
||||||
response.getWriter().close();
|
|
||||||
} else {
|
} else {
|
||||||
BulkExportJobResults results = JsonUtil.deserialize(report, BulkExportJobResults.class);
|
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
||||||
|
response.setContentType(Constants.CT_JSON);
|
||||||
|
|
||||||
// if there is a message....
|
// Create a JSON response
|
||||||
bulkResponseDocument.setMsg(results.getReportMsg());
|
BulkExportResponseJson bulkResponseDocument = new BulkExportResponseJson();
|
||||||
|
bulkResponseDocument.setTransactionTime(info.getEndTime()); // completed
|
||||||
|
|
||||||
String serverBase = getDefaultPartitionServerBase(theRequestDetails);
|
String report = info.getReport();
|
||||||
|
if (isEmpty(report)) {
|
||||||
|
// this should never happen, but just in case...
|
||||||
|
ourLog.error("No report for completed bulk export job.");
|
||||||
|
response.getWriter().close();
|
||||||
|
} else {
|
||||||
|
BulkExportJobResults results = JsonUtil.deserialize(report, BulkExportJobResults.class);
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> entrySet : results.getResourceTypeToBinaryIds().entrySet()) {
|
// if there is a message....
|
||||||
String resourceType = entrySet.getKey();
|
bulkResponseDocument.setMsg(results.getReportMsg());
|
||||||
List<String> binaryIds = entrySet.getValue();
|
|
||||||
for (String binaryId : binaryIds) {
|
String serverBase = getDefaultPartitionServerBase(theRequestDetails);
|
||||||
IIdType iId = new IdType(binaryId);
|
|
||||||
String nextUrl = serverBase + "/" + iId.toUnqualifiedVersionless().getValue();
|
for (Map.Entry<String, List<String>> entrySet : results.getResourceTypeToBinaryIds().entrySet()) {
|
||||||
bulkResponseDocument
|
String resourceType = entrySet.getKey();
|
||||||
.addOutput()
|
List<String> binaryIds = entrySet.getValue();
|
||||||
.setType(resourceType)
|
for (String binaryId : binaryIds) {
|
||||||
.setUrl(nextUrl);
|
IIdType iId = new IdType(binaryId);
|
||||||
|
String nextUrl = serverBase + "/" + iId.toUnqualifiedVersionless().getValue();
|
||||||
|
bulkResponseDocument
|
||||||
|
.addOutput()
|
||||||
|
.setType(resourceType)
|
||||||
|
.setUrl(nextUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
JsonUtil.serialize(bulkResponseDocument, response.getWriter());
|
||||||
|
response.getWriter().close();
|
||||||
}
|
}
|
||||||
JsonUtil.serialize(bulkResponseDocument, response.getWriter());
|
|
||||||
response.getWriter().close();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
|
@ -299,17 +303,35 @@ public class BulkDataExportProvider {
|
||||||
case BUILDING:
|
case BUILDING:
|
||||||
case SUBMITTED:
|
case SUBMITTED:
|
||||||
default:
|
default:
|
||||||
response.setStatus(Constants.STATUS_HTTP_202_ACCEPTED);
|
if (theRequestDetails.getRequestType() == RequestTypeEnum.DELETE) {
|
||||||
String dateString = getTransitionTimeOfJobInfo(info);
|
handleDeleteRequest(theJobId, response, info.getStatus());
|
||||||
response.addHeader(Constants.HEADER_X_PROGRESS, "Build in progress - Status set to "
|
} else {
|
||||||
+ info.getStatus()
|
response.setStatus(Constants.STATUS_HTTP_202_ACCEPTED);
|
||||||
+ " at "
|
String dateString = getTransitionTimeOfJobInfo(info);
|
||||||
+ dateString);
|
response.addHeader(Constants.HEADER_X_PROGRESS, "Build in progress - Status set to "
|
||||||
response.addHeader(Constants.HEADER_RETRY_AFTER, "120");
|
+ info.getStatus()
|
||||||
|
+ " at "
|
||||||
|
+ dateString);
|
||||||
|
response.addHeader(Constants.HEADER_RETRY_AFTER, "120");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleDeleteRequest(IPrimitiveType<String> theJobId, HttpServletResponse response, BulkExportJobStatusEnum theStatusEnum) throws IOException {
|
||||||
|
IBaseOperationOutcome outcome = OperationOutcomeUtil.newInstance(myFhirContext);
|
||||||
|
Batch2JobOperationResult resultMessage = myJobRunner.cancelInstance(theJobId.getValueAsString());
|
||||||
|
if (theStatusEnum.equals(BulkExportJobStatusEnum.COMPLETE)) {
|
||||||
|
response.setStatus(Constants.STATUS_HTTP_404_NOT_FOUND);
|
||||||
|
OperationOutcomeUtil.addIssue(myFhirContext, outcome, "error", "Job instance <" + theJobId.getValueAsString() + "> was already cancelled or has completed. Nothing to do.", null, null);
|
||||||
|
} else {
|
||||||
|
response.setStatus(Constants.STATUS_HTTP_202_ACCEPTED);
|
||||||
|
OperationOutcomeUtil.addIssue(myFhirContext, outcome, "information", resultMessage.getMessage(), null, "informational");
|
||||||
|
}
|
||||||
|
myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(outcome, response.getWriter());
|
||||||
|
response.getWriter().close();
|
||||||
|
}
|
||||||
|
|
||||||
private String getTransitionTimeOfJobInfo(Batch2JobInfo theInfo) {
|
private String getTransitionTimeOfJobInfo(Batch2JobInfo theInfo) {
|
||||||
if (theInfo.getEndTime() != null) {
|
if (theInfo.getEndTime() != null) {
|
||||||
return new InstantType(theInfo.getEndTime()).getValueAsString();
|
return new InstantType(theInfo.getEndTime()).getValueAsString();
|
||||||
|
@ -389,7 +411,7 @@ public class BulkDataExportProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> splitTypeFilters(List<IPrimitiveType<String>> theTypeFilter) {
|
private Set<String> splitTypeFilters(List<IPrimitiveType<String>> theTypeFilter) {
|
||||||
if (theTypeFilter== null) {
|
if (theTypeFilter == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +422,7 @@ public class BulkDataExportProvider {
|
||||||
Arrays
|
Arrays
|
||||||
.stream(typeFilterString.split(FARM_TO_TABLE_TYPE_FILTER_REGEX))
|
.stream(typeFilterString.split(FARM_TO_TABLE_TYPE_FILTER_REGEX))
|
||||||
.filter(StringUtils::isNotBlank)
|
.filter(StringUtils::isNotBlank)
|
||||||
.forEach(t->retVal.add(t));
|
.forEach(t -> retVal.add(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
Loading…
Reference in New Issue