Don't log broken pipe exception when client disconnects (#4399)
* Avoid error on broken pipe * Cleanup * Add changelog * Test fix * Remove fixme * Fix tests * Fix build * ddress warning * Troubleshoot code * More test logging * Add debug logging * Add logging * Roll back changes * Test fix * Address review comments
This commit is contained in:
parent
d70d813249
commit
31e4f039ff
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
title: "When running RestfulServer under heavy load or a slow network, the server sometimes
|
||||
logged an EOFException or an IOException in the system logs despite the response completing
|
||||
successfully. This has been corrected."
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.context.ConfigurationException;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
|
||||
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -47,6 +48,7 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
|
|||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.ResourceBinding;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerConfiguration;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
|
@ -214,7 +216,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv
|
|||
setUpPostConstruct();
|
||||
|
||||
Builder request = getRequest(RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA);
|
||||
IRestfulResponse response = request.build().getResponse();
|
||||
JaxRsRequest requestDetails = request.build();
|
||||
IRestfulResponse response = requestDetails.getResponse();
|
||||
response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*");
|
||||
|
||||
IBaseResource conformance;
|
||||
|
@ -239,11 +242,9 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv
|
|||
throw new ConfigurationException(Msg.code(592) + "Unsupported Fhir version: " + fhirContextVersion);
|
||||
}
|
||||
|
||||
if (conformance != null) {
|
||||
Set<SummaryEnum> summaryMode = Collections.emptySet();
|
||||
return (Response) response.streamResponseAsResource(conformance, false, summaryMode, Constants.STATUS_HTTP_200_OK, null, true, false);
|
||||
}
|
||||
return (Response) response.returnResponse(null, Constants.STATUS_HTTP_500_INTERNAL_ERROR, true, null, getResourceType().getSimpleName());
|
||||
|
||||
return (Response) RestfulServerUtils.streamResponseAsResource(this, conformance, summaryMode, Constants.STATUS_HTTP_200_OK, false, true, requestDetails, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
package ca.uhn.fhir.jaxrs.server.util;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.server.BaseParseAction;
|
||||
import ca.uhn.fhir.rest.server.BaseRestfulResponse;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.util.IoUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
|
@ -49,6 +46,12 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
*/
|
||||
public class JaxRsResponse extends BaseRestfulResponse<JaxRsRequest> {
|
||||
|
||||
private StringWriter myWriter;
|
||||
private int myStatusCode;
|
||||
private String myContentType;
|
||||
private String myCharset;
|
||||
private ByteArrayOutputStream myOutputStream;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
|
@ -62,49 +65,54 @@ public class JaxRsResponse extends BaseRestfulResponse<JaxRsRequest> {
|
|||
* The response writer is a simple String Writer. All output is configured
|
||||
* by the server.
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public Writer getResponseWriter(int theStatusCode, String theStatusMessage, String theContentType, String theCharset, boolean theRespondGzip) {
|
||||
return new StringWriter();
|
||||
public Writer getResponseWriter(int theStatusCode, String theContentType, String theCharset, boolean theRespondGzip) {
|
||||
Validate.isTrue(myWriter == null, "getResponseWriter() called multiple times");
|
||||
Validate.isTrue(myOutputStream == null, "getResponseWriter() called after getResponseOutputStream()");
|
||||
myWriter = new StringWriter();
|
||||
myStatusCode = theStatusCode;
|
||||
myContentType = theContentType;
|
||||
myCharset = theCharset;
|
||||
return myWriter;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream getResponseOutputStream(int theStatusCode, String theContentType, Integer theContentLength) {
|
||||
Validate.isTrue(myWriter == null, "getResponseOutputStream() called multiple times");
|
||||
Validate.isTrue(myOutputStream == null, "getResponseOutputStream() called after getResponseWriter()");
|
||||
|
||||
myOutputStream = new ByteArrayOutputStream();
|
||||
myStatusCode = theStatusCode;
|
||||
myContentType = theContentType;
|
||||
|
||||
return myOutputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response sendWriterResponse(int theStatus, String theContentType, String theCharset, Writer theWriter) {
|
||||
ResponseBuilder builder = buildResponse(theStatus);
|
||||
if (isNotBlank(theContentType)) {
|
||||
String charContentType = theContentType + "; charset=" + StringUtils.defaultIfBlank(theCharset, Constants.CHARSET_NAME_UTF8);
|
||||
public Response commitResponse(@Nonnull Closeable theWriterOrOutputStream) {
|
||||
IoUtil.closeQuietly(theWriterOrOutputStream);
|
||||
|
||||
ResponseBuilder builder = buildResponse(myStatusCode);
|
||||
if (isNotBlank(myContentType)) {
|
||||
if (myWriter != null) {
|
||||
String charContentType = myContentType + "; charset=" + StringUtils.defaultIfBlank(myCharset, Constants.CHARSET_NAME_UTF8);
|
||||
builder.header(Constants.HEADER_CONTENT_TYPE, charContentType);
|
||||
builder.entity(myWriter.toString());
|
||||
} else {
|
||||
byte[] byteArray = myOutputStream.toByteArray();
|
||||
if (byteArray.length > 0) {
|
||||
builder.header(Constants.HEADER_CONTENT_TYPE, myContentType);
|
||||
builder.entity(byteArray);
|
||||
}
|
||||
builder.entity(theWriter.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Response retVal = builder.build();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) {
|
||||
ResponseBuilder response = buildResponse(statusCode);
|
||||
if (bin.getContent() != null && bin.getContent().length > 0) {
|
||||
response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent());
|
||||
}
|
||||
return response.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response returnResponse(BaseParseAction<?> outcome, int operationStatus, boolean allowPrefer,
|
||||
MethodOutcome response, String resourceName) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
if (outcome != null) {
|
||||
FhirContext fhirContext = getRequestDetails().getServer().getFhirContext();
|
||||
IParser parser = RestfulServerUtils.getNewParser(fhirContext, fhirContext.getVersion().getVersion(), getRequestDetails());
|
||||
outcome.execute(parser, writer);
|
||||
}
|
||||
return sendWriterResponse(operationStatus, getParserType(), null, writer);
|
||||
}
|
||||
|
||||
protected String getParserType() {
|
||||
EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()).getEncoding();
|
||||
return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML;
|
||||
}
|
||||
|
||||
private ResponseBuilder buildResponse(int statusCode) {
|
||||
ResponseBuilder response = Response.status(statusCode);
|
||||
for (Entry<String, List<String>> header : getHeaders().entrySet()) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jaxrs.server.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
@ -61,7 +62,7 @@ public class JaxRsResponseDstu3Test {
|
|||
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, theAddContentLocationHeader, respondGzip, this.request);
|
||||
assertEquals(200, result.getStatus());
|
||||
assertEquals(contentType, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
|
||||
assertEquals(content, result.getEntity());
|
||||
assertArrayEquals(content, (byte[])result.getEntity());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jaxrs.server.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -48,7 +49,7 @@ public class JaxRsResponseTest {
|
|||
Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, theAddContentLocationHeader, respondGzip, this.request);
|
||||
assertEquals(200, result.getStatus());
|
||||
assertEquals(contentType, result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
|
||||
assertEquals(content, result.getEntity());
|
||||
assertArrayEquals(content, (byte[])result.getEntity());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -4,8 +4,6 @@ import ca.uhn.fhir.cql.BaseCqlR4Test;
|
|||
import ca.uhn.fhir.cql.r4.provider.MeasureOperationsProvider;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.MeasureReport;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
@ -41,12 +39,7 @@ public class CqlMeasureEvaluationR4ImmunizationTest extends BaseCqlR4Test {
|
|||
return this.myMeasureOperationsProvider.evaluateMeasure(new IdType("Measure", theMeasureId), evaluationDate, evaluationDate, null, "subject", thePatientRef, null, thePractitionerRef, null, null, null, null, myRequestDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled 2023-01-04 - Ticket to re-enable:
|
||||
* https://github.com/hapifhir/hapi-fhir/issues/4401
|
||||
*/
|
||||
@Test
|
||||
@Disabled
|
||||
public void test_Immunization_Ontario_Schedule() throws IOException {
|
||||
//given
|
||||
loadBundle(MY_FHIR_COMMON);
|
||||
|
|
|
@ -63,7 +63,8 @@ public class ClientThreadedCapabilitiesTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws Exception {
|
||||
ourServer.getFhirClient().registerInterceptor(myCountingMetaClientInterceptor);
|
||||
myClient = ourServer.getFhirClient();
|
||||
myClient.registerInterceptor(myCountingMetaClientInterceptor);
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,7 +93,7 @@ public class ClientThreadedCapabilitiesTest {
|
|||
|
||||
|
||||
private Object searchPatient(String last) {
|
||||
return ourServer.getFhirClient().search()
|
||||
return myClient.search()
|
||||
.forResource("Patient")
|
||||
.returnBundle(Bundle.class)
|
||||
.where(Patient.FAMILY.matches().value(last))
|
||||
|
|
|
@ -53,9 +53,11 @@ import java.util.List;
|
|||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.commons.lang3.time.DateUtils.MILLIS_PER_SECOND;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -244,14 +246,17 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
|
|||
p.setActive(true);
|
||||
IIdType pid = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
await()
|
||||
.until(()->{
|
||||
Bundle observations = myClient
|
||||
.search()
|
||||
.forResource("Observation")
|
||||
.where(Observation.SUBJECT.hasId(pid))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals(1, observations.getEntry().size());
|
||||
ourLog.info(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(observations));
|
||||
return observations.getEntry().size();
|
||||
},
|
||||
equalTo(1));
|
||||
|
||||
} finally {
|
||||
myServer.getRestfulServer().unregisterInterceptor(interceptor);
|
||||
|
|
|
@ -20,42 +20,96 @@ package ca.uhn.fhir.rest.api.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public interface IRestfulResponse {
|
||||
|
||||
Object streamResponseAsResource(IBaseResource theActualResourceToReturn, boolean thePrettyPrint, Set<SummaryEnum> theSummaryMode, int theStatusCode, String theStatusMessage, boolean theRespondGzip, boolean theAddContentLocation) throws IOException;
|
||||
|
||||
/**
|
||||
* This is only used for DSTU1 getTags operations, so it can be removed at some point when we
|
||||
* drop DSTU1
|
||||
* Implementations of this interface represent a response back to the client from the server. It is
|
||||
* conceptually similar to {@link javax.servlet.http.HttpServletResponse} but intended to be agnostic
|
||||
* of the server framework being used.
|
||||
* <p>
|
||||
* This class is a bit of an awkward abstraction given the two styles of servers it supports.
|
||||
* Servlets work by writing to a servlet response that is provided as a parameter by the container. JAX-RS
|
||||
* works by returning an object via a method return back to the containing framework. However using it correctly should
|
||||
* make for compatible code across both approaches.
|
||||
* </p>
|
||||
*/
|
||||
Object returnResponse(BaseParseAction<?> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException;
|
||||
public interface IRestfulResponse {
|
||||
|
||||
Writer getResponseWriter(int theStatusCode, String theStatusMessage, String theContentType, String theCharset, boolean theRespondGzip) throws IOException;
|
||||
/**
|
||||
* Initiate a new textual response. The Writer returned by this method must be finalized by
|
||||
* calling {@link #commitResponse(Closeable)} later.
|
||||
* <p>
|
||||
* Note that the caller should not close the returned object, but should instead just
|
||||
* return it to {@link #commitResponse(Closeable)} upon successful completion. This is
|
||||
* different from normal Java practice where you would request it in a <code>try with resource</code>
|
||||
* block, since in Servlets you are not actually required to close the writer/stream, and
|
||||
* doing so automatically may prevent you from correctly handling exceptions.
|
||||
* </p>
|
||||
*
|
||||
* @param theStatusCode The HTTP status code.
|
||||
* @param theContentType The HTTP response content type.
|
||||
* @param theCharset The HTTP response charset.
|
||||
* @param theRespondGzip Should the response be GZip encoded?
|
||||
* @return Returns a {@link Writer} that can accept the response body.
|
||||
*/
|
||||
@Nonnull
|
||||
Writer getResponseWriter(int theStatusCode, String theContentType, String theCharset, boolean theRespondGzip) throws IOException;
|
||||
|
||||
Object sendWriterResponse(int status, String contentType, String charset, Writer writer) throws IOException;
|
||||
/**
|
||||
* Initiate a new binary response. The OutputStream returned by this method must be finalized by
|
||||
* calling {@link #commitResponse(Closeable)} later. This method should only be used for non-textual
|
||||
* responses, for those use {@link #getResponseWriter(int, String, String, boolean)}.
|
||||
* <p>
|
||||
* Note that the caller should not close the returned object, but should instead just
|
||||
* return it to {@link #commitResponse(Closeable)} upon successful completion. This is
|
||||
* different from normal Java practice where you would request it in a <code>try with resource</code>
|
||||
* block, since in Servlets you are not actually required to close the writer/stream, and
|
||||
* doing so automatically may prevent you from correctly handling exceptions.
|
||||
* </p>
|
||||
*
|
||||
* @param theStatusCode The HTTP status code.
|
||||
* @param theContentType The HTTP response content type.
|
||||
* @param theContentLength If known, the number of bytes that will be written. {@literal null} otherwise.
|
||||
* @return Returns an {@link OutputStream} that can accept the response body.
|
||||
*/
|
||||
@Nonnull
|
||||
OutputStream getResponseOutputStream(int theStatusCode, String theContentType, @Nullable Integer theContentLength) throws IOException;
|
||||
|
||||
/**
|
||||
* Finalizes the response streaming using the writer that was returned by calling either
|
||||
* {@link #getResponseWriter(int, String, String, boolean)} or
|
||||
* {@link #getResponseOutputStream(int, String, Integer)}. This method should only be
|
||||
* called if the response writing/streaming actually completed successfully. If an error
|
||||
* occurred you do not need to commit the response.
|
||||
*
|
||||
* @param theWriterOrOutputStream The {@link Writer} or {@link OutputStream} that was returned by this object, or a Writer/OutputStream
|
||||
* which decorates the one returned by this object.
|
||||
* @return If the server style requires a returned response object (i.e. JAX-RS Server), this method
|
||||
* returns that object. If the server style does not require one (i.e. {@link ca.uhn.fhir.rest.server.RestfulServer}),
|
||||
* this method returns {@literal null}.
|
||||
*/
|
||||
Object commitResponse(@Nonnull Closeable theWriterOrOutputStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Adds a response header. This method must be called prior to calling
|
||||
* {@link #getResponseWriter(int, String, String, boolean)} or {@link #getResponseOutputStream(int, String, Integer)}.
|
||||
*
|
||||
* @param headerKey The header name
|
||||
* @param headerValue The header value
|
||||
*/
|
||||
void addHeader(String headerKey, String headerValue);
|
||||
|
||||
Object sendAttachmentResponse(IBaseBinary bin, int stausCode, String contentType) throws IOException;
|
||||
|
||||
void setOperationResourceLastUpdated(IPrimitiveType<Date> theOperationResourceLastUpdated);
|
||||
|
||||
/**
|
||||
* Returns the headers added to this response
|
||||
*/
|
||||
Map<String, List<String>> getHeaders();
|
||||
|
||||
void setOperationResourceId(IIdType theOperationResourceId);
|
||||
|
||||
}
|
||||
|
|
|
@ -20,30 +20,20 @@ package ca.uhn.fhir.rest.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseRestfulResponse<T extends RequestDetails> implements IRestfulResponse {
|
||||
|
||||
private IIdType myOperationResourceId;
|
||||
private IPrimitiveType<Date> myOperationResourceLastUpdated;
|
||||
private final Map<String, List<String>> myHeaders = new HashMap<>();
|
||||
private T theRequestDetails;
|
||||
private T myRequestDetails;
|
||||
|
||||
public BaseRestfulResponse(T requestDetails) {
|
||||
this.theRequestDetails = requestDetails;
|
||||
public BaseRestfulResponse(T theRequestDetails) {
|
||||
this.myRequestDetails = theRequestDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +43,7 @@ public abstract class BaseRestfulResponse<T extends RequestDetails> implements I
|
|||
|
||||
/**
|
||||
* Get the http headers
|
||||
*
|
||||
* @return the headers
|
||||
*/
|
||||
@Override
|
||||
|
@ -62,36 +53,20 @@ public abstract class BaseRestfulResponse<T extends RequestDetails> implements I
|
|||
|
||||
/**
|
||||
* Get the requestDetails
|
||||
*
|
||||
* @return the requestDetails
|
||||
*/
|
||||
public T getRequestDetails() {
|
||||
return theRequestDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOperationResourceId(IIdType theOperationResourceId) {
|
||||
myOperationResourceId = theOperationResourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOperationResourceLastUpdated(IPrimitiveType<Date> theOperationResourceLastUpdated) {
|
||||
myOperationResourceLastUpdated = theOperationResourceLastUpdated;
|
||||
return myRequestDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the requestDetails
|
||||
*
|
||||
* @param requestDetails the requestDetails to set
|
||||
*/
|
||||
public void setRequestDetails(T requestDetails) {
|
||||
this.theRequestDetails = requestDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object streamResponseAsResource(IBaseResource theResource, boolean thePrettyPrint, Set<SummaryEnum> theSummaryMode,
|
||||
int theStatusCode, String theStatusMessage, boolean theRespondGzip, boolean theAddContentLocation)
|
||||
throws IOException {
|
||||
return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResource, theSummaryMode, theStatusCode, theStatusMessage, theAddContentLocation, theRespondGzip, getRequestDetails(), myOperationResourceId, myOperationResourceLastUpdated);
|
||||
|
||||
this.myRequestDetails = requestDetails;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import ca.uhn.fhir.rest.server.method.MethodMatchEnum;
|
|||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.tenant.ITenantIdentificationStrategy;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.IoUtil;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import ca.uhn.fhir.util.UrlPathTokenizer;
|
||||
|
@ -84,6 +85,7 @@ import javax.servlet.http.HttpServlet;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Writer;
|
||||
|
@ -1166,7 +1168,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
* This is basically the end of processing for a successful request, since the
|
||||
* method binding replies to the client and closes the response.
|
||||
*/
|
||||
try (Closeable outputStreamOrWriter = (Closeable) resourceMethod.invokeServer(this, requestDetails)) {
|
||||
resourceMethod.invokeServer(this, requestDetails);
|
||||
|
||||
// Invoke interceptors
|
||||
HookParams hookParams = new HookParams();
|
||||
|
@ -1174,9 +1176,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
hookParams.add(ServletRequestDetails.class, requestDetails);
|
||||
myInterceptorService.callHooks(Pointcut.SERVER_PROCESSING_COMPLETED_NORMALLY, hookParams);
|
||||
|
||||
ourLog.trace("Done writing to stream: {}", outputStreamOrWriter);
|
||||
}
|
||||
|
||||
} catch (NotModifiedException | AuthenticationException e) {
|
||||
|
||||
HookParams handleExceptionParams = new HookParams();
|
||||
|
|
|
@ -20,9 +20,9 @@ package ca.uhn.fhir.rest.server;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
|
@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.server.method.SummaryEnumParameter;
|
|||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.BinaryUtil;
|
||||
import ca.uhn.fhir.util.DateUtils;
|
||||
import ca.uhn.fhir.util.IoUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
@ -65,6 +66,7 @@ import javax.annotation.Nonnull;
|
|||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -155,6 +157,7 @@ public class RestfulServerUtils {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases" )
|
||||
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
|
||||
// Pretty print
|
||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails);
|
||||
|
@ -885,12 +888,12 @@ public class RestfulServerUtils {
|
|||
return prettyPrint;
|
||||
}
|
||||
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int stausCode, boolean theAddContentLocationHeader,
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStatusCode, boolean theAddContentLocationHeader,
|
||||
boolean respondGzip, RequestDetails theRequestDetails) throws IOException {
|
||||
return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null);
|
||||
return streamResponseAsResource(theServer, theResource, theSummaryMode, theStatusCode, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null);
|
||||
}
|
||||
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStatusCode, String theStatusMessage,
|
||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStatusCode,
|
||||
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
||||
throws IOException {
|
||||
IRestfulResponse response = theRequestDetails.getResponse();
|
||||
|
@ -956,7 +959,16 @@ public class RestfulServerUtils {
|
|||
contentType = getBinaryContentTypeOrDefault(bin);
|
||||
response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;" );
|
||||
|
||||
return response.sendAttachmentResponse(bin, theStatusCode, contentType);
|
||||
Integer contentLength = null;
|
||||
if (bin.hasData()) {
|
||||
contentLength = bin.getContent().length;
|
||||
}
|
||||
|
||||
OutputStream outputStream = response.getResponseOutputStream(theStatusCode, contentType, contentLength);
|
||||
if (bin.hasData()) {
|
||||
outputStream.write(bin.getContent());
|
||||
}
|
||||
return response.commitResponse(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1004,7 +1016,7 @@ public class RestfulServerUtils {
|
|||
}
|
||||
String charset = Constants.CHARSET_NAME_UTF8;
|
||||
|
||||
Writer writer = response.getResponseWriter(theStatusCode, theStatusMessage, contentType, charset, respondGzip);
|
||||
Writer writer = response.getResponseWriter(theStatusCode, contentType, charset, respondGzip);
|
||||
|
||||
// Interceptor call: SERVER_OUTGOING_WRITER_CREATED
|
||||
if (theServer.getInterceptorService() != null && theServer.getInterceptorService().hasHooks(Pointcut.SERVER_OUTGOING_WRITER_CREATED)) {
|
||||
|
@ -1036,7 +1048,7 @@ public class RestfulServerUtils {
|
|||
parser.encodeResourceToWriter(theResource, writer);
|
||||
}
|
||||
|
||||
return response.sendWriterResponse(theStatusCode, contentType, charset, writer);
|
||||
return response.commitResponse(writer);
|
||||
}
|
||||
|
||||
private static String getBinaryContentTypeOrDefault(IBaseBinary theBinary) {
|
||||
|
@ -1064,7 +1076,8 @@ public class RestfulServerUtils {
|
|||
String contentType = theBinary.getContentType();
|
||||
if (theResponseEncoding == null) {
|
||||
return true;
|
||||
} if (isBlank(contentType)) {
|
||||
}
|
||||
if (isBlank(contentType)) {
|
||||
return Constants.CT_OCTET_STREAM.equals(theResponseEncoding.getContentType());
|
||||
} else if (contentType.equalsIgnoreCase(theResponseEncoding.getContentType())) {
|
||||
return true;
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -39,6 +40,7 @@ import ca.uhn.fhir.interceptor.api.Hook;
|
|||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRestfulResponse;
|
||||
|
@ -61,12 +63,12 @@ public class ExceptionHandlingInterceptor {
|
|||
|
||||
public static final String PROCESSING = Constants.OO_INFOSTATUS_PROCESSING;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptor.class);
|
||||
public static final Set<SummaryEnum> SUMMARY_MODE = Collections.singleton(SummaryEnum.FALSE);
|
||||
private Class<?>[] myReturnStackTracesForExceptionTypes;
|
||||
|
||||
@Hook(Pointcut.SERVER_HANDLE_EXCEPTION)
|
||||
public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
|
||||
Closeable writer = (Closeable) handleException(theRequestDetails, theException);
|
||||
writer.close();
|
||||
handleException(theRequestDetails, theException);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -110,8 +112,7 @@ public class ExceptionHandlingInterceptor {
|
|||
ourLog.error("HAPI-FHIR was unable to reset the output stream during exception handling. The root causes follows:", t);
|
||||
}
|
||||
|
||||
return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, statusMessage, false, false);
|
||||
|
||||
return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), oo, SUMMARY_MODE, statusCode, false, false, theRequestDetails, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,10 +36,12 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
@ -219,12 +221,14 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
|||
|
||||
IRestfulResponse restfulResponse = theRequest.getResponse();
|
||||
|
||||
IIdType responseId = null;
|
||||
IPrimitiveType<Date> operationResourceLastUpdated = null;
|
||||
if (theMethodOutcome != null) {
|
||||
if (theMethodOutcome.getResource() != null) {
|
||||
restfulResponse.setOperationResourceLastUpdated(RestfulServerUtils.extractLastUpdatedFromResource(theMethodOutcome.getResource()));
|
||||
operationResourceLastUpdated = RestfulServerUtils.extractLastUpdatedFromResource(theMethodOutcome.getResource());
|
||||
}
|
||||
|
||||
IIdType responseId = theMethodOutcome.getId();
|
||||
responseId = theMethodOutcome.getId();
|
||||
if (responseId != null && responseId.getResourceType() == null && responseId.hasIdPart()) {
|
||||
responseId = responseId.withResourceType(getResourceName());
|
||||
}
|
||||
|
@ -232,14 +236,11 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
|||
if (responseId != null) {
|
||||
String serverBase = theRequest.getFhirServerBase();
|
||||
responseId = RestfulServerUtils.fullyQualifyResourceIdOrReturnNull(theServer, theMethodOutcome.getResource(), serverBase, responseId);
|
||||
restfulResponse.setOperationResourceId(responseId);
|
||||
}
|
||||
}
|
||||
|
||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
||||
Set<SummaryEnum> summaryMode = Collections.emptySet();
|
||||
|
||||
return restfulResponse.streamResponseAsResource(responseDetails.getResponseResource(), prettyPrint, summaryMode, responseDetails.getResponseCode(), null, theRequest.isRespondGzip(), true);
|
||||
return RestfulServerUtils.streamResponseAsResource(theServer, responseDetails.getResponseResource(), summaryMode, responseDetails.getResponseCode(), true, theRequest.isRespondGzip(), theRequest, responseId, operationResourceLastUpdated);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -445,9 +445,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
if (!callOutgoingResponseHook(theRequest, responseDetails)) {
|
||||
return null;
|
||||
}
|
||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
||||
|
||||
return theRequest.getResponse().streamResponseAsResource(responseDetails.getResponseResource(), prettyPrint, summaryMode, responseDetails.getResponseCode(), null, theRequest.isRespondGzip(), isAddContentLocationHeader());
|
||||
return RestfulServerUtils.streamResponseAsResource(theServer, responseDetails.getResponseResource(), summaryMode, responseDetails.getResponseCode(), isAddContentLocationHeader(), theRequest.isRespondGzip(), theRequest, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ public class GraphQLMethodBinding extends OperationMethodBinding {
|
|||
}
|
||||
|
||||
// Write the response
|
||||
Writer writer = theRequest.getResponse().getResponseWriter(statusCode, statusMessage, contentType, charset, respondGzip);
|
||||
Writer writer = theRequest.getResponse().getResponseWriter(statusCode, contentType, charset, respondGzip);
|
||||
writer.write(responseString);
|
||||
writer.close();
|
||||
|
||||
|
|
|
@ -21,14 +21,15 @@ package ca.uhn.fhir.rest.server.servlet;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.server.BaseParseAction;
|
||||
import ca.uhn.fhir.rest.server.BaseRestfulResponse;
|
||||
import ca.uhn.fhir.util.IoUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
@ -40,6 +41,9 @@ import java.util.zip.GZIPOutputStream;
|
|||
|
||||
public class ServletRestfulResponse extends BaseRestfulResponse<ServletRequestDetails> {
|
||||
|
||||
private Writer myWriter;
|
||||
private OutputStream myOutputStream;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -47,24 +51,29 @@ public class ServletRestfulResponse extends BaseRestfulResponse<ServletRequestDe
|
|||
super(servletRequestDetails);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream sendAttachmentResponse(IBaseBinary theBinary, int theStatusCode, String contentType) throws IOException {
|
||||
public OutputStream getResponseOutputStream(int theStatusCode, String theContentType, Integer theContentLength) throws IOException {
|
||||
Validate.isTrue(myWriter == null, "getResponseOutputStream() called multiple times" );
|
||||
Validate.isTrue(myOutputStream == null, "getResponseOutputStream() called after getResponseWriter()" );
|
||||
|
||||
addHeaders();
|
||||
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
|
||||
theHttpResponse.setStatus(theStatusCode);
|
||||
theHttpResponse.setContentType(contentType);
|
||||
theHttpResponse.setCharacterEncoding(null);
|
||||
if (theBinary.getContent() == null || theBinary.getContent().length == 0) {
|
||||
return theHttpResponse.getOutputStream();
|
||||
HttpServletResponse httpResponse = getRequestDetails().getServletResponse();
|
||||
httpResponse.setStatus(theStatusCode);
|
||||
httpResponse.setContentType(theContentType);
|
||||
httpResponse.setCharacterEncoding(null);
|
||||
if (theContentLength != null) {
|
||||
httpResponse.setContentLength(theContentLength);
|
||||
}
|
||||
theHttpResponse.setContentLength(theBinary.getContent().length);
|
||||
ServletOutputStream oos = theHttpResponse.getOutputStream();
|
||||
oos.write(theBinary.getContent());
|
||||
return oos;
|
||||
myOutputStream = httpResponse.getOutputStream();
|
||||
return myOutputStream;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Writer getResponseWriter(int theStatusCode, String theStatusMessage, String theContentType, String theCharset, boolean theRespondGzip) throws IOException {
|
||||
public Writer getResponseWriter(int theStatusCode, String theContentType, String theCharset, boolean theRespondGzip) throws IOException {
|
||||
Validate.isTrue(myOutputStream == null, "getResponseWriter() called after getResponseOutputStream()" );
|
||||
|
||||
addHeaders();
|
||||
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
|
||||
theHttpResponse.setCharacterEncoding(theCharset);
|
||||
|
@ -72,16 +81,18 @@ public class ServletRestfulResponse extends BaseRestfulResponse<ServletRequestDe
|
|||
theHttpResponse.setContentType(theContentType);
|
||||
if (theRespondGzip) {
|
||||
theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
|
||||
return new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), StandardCharsets.UTF_8);
|
||||
ServletOutputStream outputStream = theHttpResponse.getOutputStream();
|
||||
myWriter = new OutputStreamWriter(new GZIPOutputStream(outputStream), StandardCharsets.UTF_8);
|
||||
return myWriter;
|
||||
}
|
||||
|
||||
// return new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), StandardCharsets.UTF_8);
|
||||
return theHttpResponse.getWriter();
|
||||
myWriter = theHttpResponse.getWriter();
|
||||
return myWriter;
|
||||
}
|
||||
|
||||
private void addHeaders() {
|
||||
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
|
||||
getRequestDetails().getServer().addHeadersToResponse(theHttpResponse);
|
||||
HttpServletResponse httpResponse = getRequestDetails().getServletResponse();
|
||||
getRequestDetails().getServer().addHeadersToResponse(httpResponse);
|
||||
for (Entry<String, List<String>> header : getHeaders().entrySet()) {
|
||||
String key = header.getKey();
|
||||
key = sanitizeHeaderField(key);
|
||||
|
@ -91,27 +102,23 @@ public class ServletRestfulResponse extends BaseRestfulResponse<ServletRequestDe
|
|||
|
||||
// existing headers should be overridden
|
||||
if (first) {
|
||||
theHttpResponse.setHeader(key, value);
|
||||
httpResponse.setHeader(key, value);
|
||||
first = false;
|
||||
} else {
|
||||
theHttpResponse.addHeader(key, value);
|
||||
httpResponse.addHeader(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object commitResponse(@Nonnull Closeable theWriterOrOutputStream) {
|
||||
IoUtil.closeQuietly(theWriterOrOutputStream);
|
||||
return null;
|
||||
}
|
||||
|
||||
static String sanitizeHeaderField(String theKey) {
|
||||
return StringUtils.replaceChars(theKey, "\r\n", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Writer sendWriterResponse(int theStatus, String theContentType, String theCharset, Writer theWriter) {
|
||||
return theWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object returnResponse(BaseParseAction<?> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException {
|
||||
addHeaders();
|
||||
return getRequestDetails().getServer().returnResponse(getRequestDetails(), outcome, operationStatus, allowPrefer, response, resourceName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public class ServletRestfulResponseTest {
|
|||
response.addHeader("Authorization", "Bearer");
|
||||
response.addHeader("Cache-Control", "no-cache, no-store");
|
||||
|
||||
response.getResponseWriter(200, "Status", "text/plain", "UTF-8", false);
|
||||
response.getResponseWriter(200, "text/plain", "UTF-8", false);
|
||||
|
||||
final InOrder orderVerifier = Mockito.inOrder(servletResponse);
|
||||
orderVerifier.verify(servletResponse).setHeader(eq("Authorization"), eq("Basic"));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||
|
@ -11,54 +12,38 @@ import ca.uhn.fhir.rest.annotation.ResourceParam;
|
|||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CustomTypeServerR4 {
|
||||
private static CloseableHttpClient ourClient;
|
||||
public class CustomTypeServerR4Test {
|
||||
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static String ourLastConditionalUrl;
|
||||
private static IdType ourLastId;
|
||||
private static IdType ourLastIdParam;
|
||||
private static boolean ourLastRequestWasSearch;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CustomTypeServerR4.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
ourLastId = null;
|
||||
ourLastConditionalUrl = null;
|
||||
ourLastIdParam = null;
|
||||
ourLastRequestWasSearch = false;
|
||||
}
|
||||
private static FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
@RegisterExtension
|
||||
private static RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
.registerProvider(new PatientProvider())
|
||||
.keepAliveBetweenTests();
|
||||
@RegisterExtension
|
||||
private static HttpClientExtension ourClient = new HttpClientExtension();
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CustomTypeServerR4Test.class);
|
||||
|
||||
@Test
|
||||
public void testCreateWithIdInBody() throws Exception {
|
||||
|
@ -67,8 +52,7 @@ public class CustomTypeServerR4 {
|
|||
patient.setId("2");
|
||||
patient.addIdentifier().setValue("002");
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||
// httpPost.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=system%7C001");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient");
|
||||
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
@ -87,8 +71,7 @@ public class CustomTypeServerR4 {
|
|||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setValue("002");
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/2");
|
||||
// httpPost.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=system%7C001");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/2");
|
||||
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
@ -100,7 +83,7 @@ public class CustomTypeServerR4 {
|
|||
|
||||
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
|
||||
assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(Msg.code(365) + "Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", oo.getIssue().get(0).getDiagnostics());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -109,7 +92,7 @@ public class CustomTypeServerR4 {
|
|||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setValue("002");
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/2");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/2");
|
||||
httpPost.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=system%7C001");
|
||||
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
|
||||
|
@ -122,45 +105,19 @@ public class CustomTypeServerR4 {
|
|||
|
||||
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
|
||||
assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", oo.getIssue().get(0).getDiagnostics());
|
||||
assertEquals(Msg.code(365) + "Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", oo.getIssue().get(0).getDiagnostics());
|
||||
}
|
||||
|
||||
|
||||
@AfterAll
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
TestUtil.randomizeLocaleAndTimezone();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
ourServer = new Server(0);
|
||||
|
||||
PatientProvider patientProvider = new PatientProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Create()
|
||||
public MethodOutcome createPatient(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditional, @IdParam IdType theIdParam) {
|
||||
ourLastConditionalUrl = theConditional;
|
||||
ourLastId = thePatient.getIdElement();
|
||||
ourLastIdParam = theIdParam;
|
||||
return new MethodOutcome(new IdType("Patient/001/_history/002"));
|
||||
}
|
||||
|
||||
|
@ -171,7 +128,6 @@ public class CustomTypeServerR4 {
|
|||
|
||||
@Search
|
||||
public List<IResource> search(@OptionalParam(name = "foo") StringDt theString) {
|
||||
ourLastRequestWasSearch = true;
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -25,30 +26,15 @@ import org.apache.http.entity.ByteArrayEntity;
|
|||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Binary;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.CapabilityStatement;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.IntegerType;
|
||||
import org.hl7.fhir.r4.model.MoneyQuantity;
|
||||
import org.hl7.fhir.r4.model.OperationDefinition;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.UnsignedIntType;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -56,7 +42,6 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInRelativeOrder;
|
||||
|
@ -68,10 +53,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
public class OperationServerR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerR4Test.class);
|
||||
private static final String TEXT_HTML = "text/html";
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
@RegisterExtension
|
||||
private static final RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
.registerProvider(new PatientProvider())
|
||||
.registerProvider(new PlainProvider())
|
||||
.withPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2))
|
||||
.withDefaultResponseEncoding(EncodingEnum.XML);
|
||||
@RegisterExtension
|
||||
private static final HttpClientExtension ourClient = new HttpClientExtension();
|
||||
private static IdType ourLastId;
|
||||
private static String ourLastMethod;
|
||||
private static StringType ourLastParam1;
|
||||
|
@ -79,8 +72,6 @@ public class OperationServerR4Test {
|
|||
private static List<StringType> ourLastParam3;
|
||||
private static MoneyQuantity ourLastParamMoney1;
|
||||
private static UnsignedIntType ourLastParamUnsignedInt1;
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static IBaseResource ourNextResponse;
|
||||
private static RestOperationTypeEnum ourLastRestOperation;
|
||||
private IGenericClient myFhirClient;
|
||||
|
@ -97,7 +88,7 @@ public class OperationServerR4Test {
|
|||
ourNextResponse = null;
|
||||
ourLastRestOperation = null;
|
||||
|
||||
myFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
||||
myFhirClient = ourServer.getFhirClient();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -175,7 +166,7 @@ public class OperationServerR4Test {
|
|||
patient.addIdentifier().setSystem("SYSTEM" ).setValue("VALUE" );
|
||||
bundle.addEntry().setResource(patient);
|
||||
|
||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RETURNING_BUNDLE"
|
||||
HttpGet httpPost = new HttpGet(ourServer.getBaseUrl() + "/Patient/$OP_TYPE_RETURNING_BUNDLE"
|
||||
+ "?_pretty=true&_elements=identifier" );
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
|
||||
|
@ -194,7 +185,7 @@ public class OperationServerR4Test {
|
|||
public void testManualResponseWithPrimitiveParam() throws Exception {
|
||||
|
||||
// Try with a GET
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$manualResponseWithPrimitiveParam?path=THIS_IS_A_PATH");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/123/$manualResponseWithPrimitiveParam?path=THIS_IS_A_PATH" );
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
|
@ -210,7 +201,7 @@ public class OperationServerR4Test {
|
|||
public void testInstanceEverythingGet() throws Exception {
|
||||
|
||||
// Try with a GET
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$everything");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/123/$everything" );
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
|
@ -226,7 +217,7 @@ public class OperationServerR4Test {
|
|||
public void testInstanceOnPlainProvider() throws Exception {
|
||||
|
||||
// Try with a GET
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_PLAIN_PROVIDER_ON_INSTANCE");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/123/$OP_PLAIN_PROVIDER_ON_INSTANCE" );
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
|
@ -240,7 +231,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testInstanceEverythingHapiClient() {
|
||||
ourCtx.newRestfulGenericClient("http://localhost:" + ourPort).operation().onInstance(new IdType("Patient/123")).named("$everything").withParameters(new Parameters()).execute();
|
||||
ourCtx.newRestfulGenericClient(ourServer.getBaseUrl()).operation().onInstance(new IdType("Patient/123" )).named("$everything" ).withParameters(new Parameters()).execute();
|
||||
|
||||
assertEquals("instance $everything", ourLastMethod);
|
||||
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
|
||||
|
@ -251,7 +242,7 @@ public class OperationServerR4Test {
|
|||
@Test
|
||||
public void testInstanceVersionEverythingHapiClient() {
|
||||
ourCtx
|
||||
.newRestfulGenericClient("http://localhost:" + ourPort)
|
||||
.newRestfulGenericClient(ourServer.getBaseUrl())
|
||||
.operation()
|
||||
.onInstanceVersion(new IdType("Patient/123/_history/456" ))
|
||||
.named("$everything" )
|
||||
|
@ -269,7 +260,7 @@ public class OperationServerR4Test {
|
|||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(new Parameters());
|
||||
|
||||
// Try with a POST
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$everything");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/123/$everything" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -288,7 +279,7 @@ public class OperationServerR4Test {
|
|||
byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
|
||||
ContentType contentType = ContentType.IMAGE_PNG;
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutput");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$manualInputAndOutput" );
|
||||
httpPost.setEntity(new ByteArrayEntity(bytes, contentType));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
|
||||
|
@ -307,7 +298,7 @@ public class OperationServerR4Test {
|
|||
byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
|
||||
ContentType contentType = ContentType.IMAGE_PNG;
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutputWithParam?param1=value");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$manualInputAndOutputWithParam?param1=value" );
|
||||
httpPost.setEntity(new ByteArrayEntity(bytes, contentType));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
|
||||
|
@ -324,7 +315,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testOperationCantUseGetIfItIsntIdempotent() throws Exception {
|
||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||
HttpGet httpPost = new HttpGet(ourServer.getBaseUrl() + "/Patient/123/$OP_INSTANCE" );
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
assertEquals(Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED, status.getStatusLine().getStatusCode());
|
||||
|
@ -341,7 +332,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM1" ).setValue(new IntegerType(123));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/123/$OP_INSTANCE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
|
@ -360,7 +351,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/123/$OP_INSTANCE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
|
||||
|
@ -380,7 +371,7 @@ public class OperationServerR4Test {
|
|||
* Against type should fail
|
||||
*/
|
||||
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE");
|
||||
httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_INSTANCE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
|
||||
|
@ -399,7 +390,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE_OR_TYPE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/123/$OP_INSTANCE_OR_TYPE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -424,7 +415,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE_OR_TYPE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_INSTANCE_OR_TYPE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -448,7 +439,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/$OP_SERVER" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -471,7 +462,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER_WITH_RAW_STRING");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/$OP_SERVER_WITH_RAW_STRING" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -494,7 +485,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_TYPE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -517,7 +508,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RET_BUNDLE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_TYPE_RET_BUNDLE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -535,7 +526,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testOperationWithBundleProviderResponse() throws Exception {
|
||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/$OP_SERVER_BUNDLE_PROVIDER?_pretty=true");
|
||||
HttpGet httpPost = new HttpGet(ourServer.getBaseUrl() + "/$OP_SERVER_BUNDLE_PROVIDER?_pretty=true" );
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -548,7 +539,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testOperationWithGetUsingParams() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/$OP_TYPE?PARAM1=PARAM1val" );
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -565,7 +556,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testOperationWithGetUsingParamsFailsWithNonPrimitive() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val&PARAM2=foo");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/$OP_TYPE?PARAM1=PARAM1val&PARAM2=foo" );
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
|
||||
assertEquals(405, status.getStatusLine().getStatusCode());
|
||||
|
@ -584,7 +575,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM3" ).setValue(new StringType("PARAM3val2" ));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER_LIST_PARAM");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/$OP_SERVER_LIST_PARAM" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -609,7 +600,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM1" ).setValue(new IntegerType("123" ));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_PROFILE_DT" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -630,7 +621,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM1" ).setValue(money);
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT2");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_PROFILE_DT2" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -645,7 +636,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testOperationWithProfileDatatypeUrl() throws Exception {
|
||||
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT?PARAM1=123");
|
||||
HttpGet httpPost = new HttpGet(ourServer.getBaseUrl() + "/Patient/$OP_PROFILE_DT?PARAM1=123" );
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -662,7 +653,7 @@ public class OperationServerR4Test {
|
|||
p.addParameter().setName("PARAM2" ).setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
|
||||
HttpPost httpPost = new HttpPost(ourServer.getBaseUrl() + "/Patient/$OP_TYPE" );
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8" )));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
|
@ -678,7 +669,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testReadWithOperations() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient/123" );
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -689,7 +680,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testReturnBinaryWithAcceptFhir() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$binaryop");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/$binaryop?_pretty=false" );
|
||||
httpGet.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON_NON_LEGACY);
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -702,7 +693,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@Test
|
||||
public void testReturnBinaryWithAcceptHtml() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$binaryop");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/$binaryop" );
|
||||
httpGet.addHeader(Constants.HEADER_ACCEPT, TEXT_HTML);
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
@ -1001,36 +992,7 @@ public class OperationServerR4Test {
|
|||
|
||||
@AfterAll
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
TestUtil.randomizeLocaleAndTimezone();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
ourCtx = FhirContext.forR4();
|
||||
ourServer = new Server(0);
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
||||
|
||||
servlet.setFhirContext(ourCtx);
|
||||
PlainProvider plainProvider = new PlainProvider();
|
||||
PatientProvider patientProvider = new PatientProvider();
|
||||
servlet.registerProviders(patientProvider, plainProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import com.helger.commons.collection.iterate.EmptyEnumeration;
|
||||
import org.apache.commons.collections4.iterators.IteratorEnumeration;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ServerConcurrencyTest {
|
||||
|
||||
private static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ServerConcurrencyTest.class);
|
||||
@RegisterExtension
|
||||
private static final RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
.registerProvider(new MyPatientProvider());
|
||||
@RegisterExtension
|
||||
private final HttpClientExtension myHttpClient = new HttpClientExtension();
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest myRequest;
|
||||
@Mock
|
||||
private HttpServletResponse myResponse;
|
||||
@Mock
|
||||
private PrintWriter myWriter;
|
||||
private HashMap<String, String> myHeaders;
|
||||
|
||||
@Test
|
||||
public void testExceptionClosingInputStream() throws IOException {
|
||||
initRequestMocks();
|
||||
DelegatingServletInputStream inputStream = createMockPatientBodyServletInputStream();
|
||||
inputStream.setExceptionOnClose(true);
|
||||
when(myRequest.getInputStream()).thenReturn(inputStream);
|
||||
when(myResponse.getWriter()).thenReturn(myWriter);
|
||||
|
||||
assertDoesNotThrow(() ->
|
||||
ourServer.getRestfulServer().handleRequest(RequestTypeEnum.POST, myRequest, myResponse)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionClosingOutputStream() throws IOException {
|
||||
initRequestMocks();
|
||||
when(myRequest.getInputStream()).thenReturn(createMockPatientBodyServletInputStream());
|
||||
when(myResponse.getWriter()).thenReturn(myWriter);
|
||||
|
||||
// Throw an exception when the stream is closed
|
||||
doThrow(new EOFException()).when(myWriter).close();
|
||||
|
||||
assertDoesNotThrow(() ->
|
||||
ourServer.getRestfulServer().handleRequest(RequestTypeEnum.POST, myRequest, myResponse)
|
||||
);
|
||||
}
|
||||
|
||||
private void initRequestMocks() {
|
||||
myHeaders = new HashMap<>();
|
||||
myHeaders.put(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW);
|
||||
|
||||
when(myRequest.getRequestURI()).thenReturn("/Patient");
|
||||
when(myRequest.getRequestURL()).thenReturn(new StringBuffer(ourServer.getBaseUrl() + "/Patient"));
|
||||
when(myRequest.getHeader(any())).thenAnswer(t -> {
|
||||
String header = t.getArgument(0, String.class);
|
||||
String value = myHeaders.get(header);
|
||||
ourLog.info("Request for header '{}' produced: {}", header, value);
|
||||
return value;
|
||||
});
|
||||
when(myRequest.getHeaders(any())).thenAnswer(t -> {
|
||||
String header = t.getArgument(0, String.class);
|
||||
String value = myHeaders.get(header);
|
||||
ourLog.info("Request for header '{}' produced: {}", header, value);
|
||||
if (value != null) {
|
||||
return new IteratorEnumeration<>(Collections.singleton(value).iterator());
|
||||
}
|
||||
return new EmptyEnumeration<>();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the class from Spring Test with the same name
|
||||
*/
|
||||
public static class DelegatingServletInputStream extends ServletInputStream {
|
||||
private final InputStream mySourceStream;
|
||||
private boolean myFinished = false;
|
||||
private boolean myExceptionOnClose = false;
|
||||
|
||||
public DelegatingServletInputStream(InputStream sourceStream) {
|
||||
Assert.notNull(sourceStream, "Source InputStream must not be null");
|
||||
this.mySourceStream = sourceStream;
|
||||
}
|
||||
|
||||
public void setExceptionOnClose(boolean theExceptionOnClose) {
|
||||
myExceptionOnClose = theExceptionOnClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int data = this.mySourceStream.read();
|
||||
if (data == -1) {
|
||||
this.myFinished = true;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return this.mySourceStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.mySourceStream.close();
|
||||
if (myExceptionOnClose) {
|
||||
throw new IOException("Failed!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return this.myFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class MyPatientProvider implements IResourceProvider {
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam Patient thePatient) throws InterruptedException {
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setDiagnostics(RandomStringUtils.randomAlphanumeric(1000));
|
||||
|
||||
return new MethodOutcome()
|
||||
.setId(new IdType("Patient/A"))
|
||||
.setOperationOutcome(oo);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static DelegatingServletInputStream createMockPatientBodyServletInputStream() {
|
||||
Patient input = new Patient();
|
||||
input.addName().setFamily(RandomStringUtils.randomAlphanumeric(100000));
|
||||
String patient = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(patient.getBytes(StandardCharsets.UTF_8));
|
||||
return new DelegatingServletInputStream(bais);
|
||||
}
|
||||
}
|
|
@ -2,11 +2,14 @@ package ca.uhn.fhir.rest.server.interceptor;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
|
@ -19,8 +22,11 @@ import ca.uhn.fhir.rest.server.interceptor.consent.IConsentService;
|
|||
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
import ca.uhn.fhir.test.utilities.LoggingExtension;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.helger.commons.collection.iterate.EmptyEnumeration;
|
||||
import org.apache.commons.collections4.iterators.IteratorEnumeration;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -41,16 +47,28 @@ import org.mockito.ArgumentCaptor;
|
|||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.timeout;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
@ -65,13 +83,15 @@ public class ConsentInterceptorTest {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ConsentInterceptorTest.class);
|
||||
@RegisterExtension
|
||||
private final HttpClientExtension myClient = new HttpClientExtension();
|
||||
@RegisterExtension
|
||||
private final LoggingExtension myLoggingExtension = new LoggingExtension();
|
||||
private static final FhirContext ourCtx = FhirContext.forR4Cached();
|
||||
private int myPort;
|
||||
private static final DummyPatientResourceProvider ourPatientProvider = new DummyPatientResourceProvider(ourCtx);
|
||||
private static final DummySystemProvider ourSystemProvider = new DummySystemProvider();
|
||||
|
||||
@RegisterExtension
|
||||
private static RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
private static final RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
.registerProvider(ourPatientProvider)
|
||||
.registerProvider(ourSystemProvider)
|
||||
.withPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
@ -129,8 +149,8 @@ public class ConsentInterceptorTest {
|
|||
ourLog.info("Response: {}", responseContent);
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -243,10 +263,10 @@ public class ConsentInterceptorTest {
|
|||
ourLog.info("Response: {}", responseContent);
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(0)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(0)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(2)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).completeOperationSuccess(any(), any());
|
||||
}
|
||||
|
||||
|
||||
|
@ -268,12 +288,12 @@ public class ConsentInterceptorTest {
|
|||
assertThat(responseContent, containsString("PTA"));
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -296,12 +316,12 @@ public class ConsentInterceptorTest {
|
|||
assertThat(responseContent, containsString("PTA"));
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -329,7 +349,7 @@ public class ConsentInterceptorTest {
|
|||
}
|
||||
|
||||
verify(myConsentSvc, timeout(10000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(10000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(10000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(10000).times(1)).completeOperationSuccess(any(), any());
|
||||
|
@ -357,12 +377,12 @@ public class ConsentInterceptorTest {
|
|||
assertNull(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE));
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any()); // the two patients + the bundle
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(3)).willSeeResource(any(), any(), any()); // the two patients + the bundle
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -398,11 +418,11 @@ public class ConsentInterceptorTest {
|
|||
}
|
||||
|
||||
verify(myConsentSvc, timeout(1000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(1000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(1000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(1000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -438,12 +458,12 @@ public class ConsentInterceptorTest {
|
|||
assertEquals("PTB", response.getEntry().get(1).getResource().getIdElement().getIdPart());
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(4)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(4)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -476,12 +496,12 @@ public class ConsentInterceptorTest {
|
|||
assertEquals("PTB", response.getEntry().get(1).getResource().getIdElement().getIdPart());
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -529,7 +549,7 @@ public class ConsentInterceptorTest {
|
|||
assertEquals("REPLACEMENT", ((Patient)response.getEntry().get(0).getResource()).getIdentifierFirstRep().getSystem());
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
}
|
||||
|
||||
|
||||
|
@ -554,18 +574,18 @@ public class ConsentInterceptorTest {
|
|||
assertNull(response.getTotalElement().getValue());
|
||||
assertEquals(0, response.getEntry().size());
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
verifyNoMoreInteractions(myConsentSvc2);
|
||||
}
|
||||
|
@ -602,18 +622,18 @@ public class ConsentInterceptorTest {
|
|||
assertThat(responseContent, not(containsString("\"entry\"")));
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(2)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(2)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(3)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(2)).willSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
}
|
||||
|
||||
|
@ -638,18 +658,18 @@ public class ConsentInterceptorTest {
|
|||
assertNull(response.getTotalElement().getValue());
|
||||
assertEquals(1, response.getEntry().size());
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
verifyNoMoreInteractions(myConsentSvc2);
|
||||
}
|
||||
|
@ -676,18 +696,18 @@ public class ConsentInterceptorTest {
|
|||
assertNull(response.getTotalElement().getValue());
|
||||
assertEquals(0, response.getEntry().size());
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
verifyNoMoreInteractions(myConsentSvc2);
|
||||
}
|
||||
|
@ -719,22 +739,111 @@ public class ConsentInterceptorTest {
|
|||
Patient patient = (Patient) response.getEntry().get(0).getResource();
|
||||
assertEquals(2, patient.getIdentifier().size());
|
||||
|
||||
verify(myConsentSvc, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, times(2)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, times(2)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).startOperation(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).shouldProcessCanSeeResource(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).canSeeResource(any(), any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(2)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc2, timeout(2000).times(2)).willSeeResource(any(), any(), any()); // On bundle
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(1)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verify(myConsentSvc2, timeout(2000).times(0)).completeOperationFailure(any(), any(), any());
|
||||
verifyNoMoreInteractions(myConsentSvc);
|
||||
verifyNoMoreInteractions(myConsentSvc2);
|
||||
}
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest myRequest;
|
||||
@Mock
|
||||
private HttpServletResponse myResponse;
|
||||
@Mock
|
||||
private PrintWriter myWriter;
|
||||
private HashMap<String, String> myHeaders;
|
||||
|
||||
private void initRequestMocks() {
|
||||
myHeaders = new HashMap<>();
|
||||
myHeaders.put(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW);
|
||||
|
||||
when(myRequest.getRequestURI()).thenReturn("/Patient");
|
||||
when(myRequest.getRequestURL()).thenReturn(new StringBuffer(ourServer.getBaseUrl() + "/Patient"));
|
||||
when(myRequest.getHeader(any())).thenAnswer(t -> {
|
||||
String header = t.getArgument(0, String.class);
|
||||
String value = myHeaders.get(header);
|
||||
ourLog.info("Request for header '{}' produced: {}", header, value);
|
||||
return value;
|
||||
});
|
||||
when(myRequest.getHeaders(any())).thenAnswer(t -> {
|
||||
String header = t.getArgument(0, String.class);
|
||||
String value = myHeaders.get(header);
|
||||
ourLog.info("Request for header '{}' produced: {}", header, value);
|
||||
if (value != null) {
|
||||
return new IteratorEnumeration<>(Collections.singleton(value).iterator());
|
||||
}
|
||||
return new EmptyEnumeration<>();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the class from Spring Test with the same name
|
||||
*/
|
||||
public static class DelegatingServletInputStream extends ServletInputStream {
|
||||
private final InputStream mySourceStream;
|
||||
private boolean myFinished = false;
|
||||
|
||||
public void setExceptionOnClose(boolean theExceptionOnClose) {
|
||||
myExceptionOnClose = theExceptionOnClose;
|
||||
}
|
||||
|
||||
private boolean myExceptionOnClose = false;
|
||||
|
||||
public DelegatingServletInputStream(InputStream sourceStream) {
|
||||
Assert.notNull(sourceStream, "Source InputStream must not be null");
|
||||
this.mySourceStream = sourceStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int data = this.mySourceStream.read();
|
||||
if (data == -1) {
|
||||
this.myFinished = true;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return this.mySourceStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.mySourceStream.close();
|
||||
if (myExceptionOnClose) {
|
||||
throw new IOException("Failed!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return this.myFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutcomeException() throws IOException {
|
||||
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
|
||||
|
@ -747,8 +856,8 @@ public class ConsentInterceptorTest {
|
|||
ourLog.info("Response: {}", responseContent);
|
||||
}
|
||||
|
||||
verify(myConsentSvc, times(0)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, times(1)).completeOperationFailure(any(), myExceptionCaptor.capture(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(0)).completeOperationSuccess(any(), any());
|
||||
verify(myConsentSvc, timeout(2000).times(1)).completeOperationFailure(any(), myExceptionCaptor.capture(), any());
|
||||
|
||||
assertEquals(Msg.code(389) + "Failed to call access method: java.lang.NullPointerException: A MESSAGE", myExceptionCaptor.getValue().getMessage());
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.test.utilities.HttpClientExtension;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -35,6 +37,7 @@ import org.junit.jupiter.api.AfterAll;
|
|||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -46,36 +49,42 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
public class ExceptionHandlingInterceptorTest {
|
||||
|
||||
private static ExceptionHandlingInterceptor myInterceptor;
|
||||
private static final String OPERATION_OUTCOME_DETAILS = "OperationOutcomeDetails";
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptorTest.class);
|
||||
|
||||
@RegisterExtension
|
||||
private static final RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
|
||||
.withDefaultResponseEncoding(EncodingEnum.XML)
|
||||
.registerProvider(new DummyPatientResourceProvider());
|
||||
@RegisterExtension
|
||||
private static final HttpClientExtension ourClient = new HttpClientExtension();
|
||||
|
||||
private ExceptionHandlingInterceptor myInterceptor;
|
||||
private static final String OPERATION_OUTCOME_DETAILS = "OperationOutcomeDetails";
|
||||
private static Class<? extends Exception> ourExceptionType;
|
||||
private static boolean ourGenerateOperationOutcome;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionHandlingInterceptorTest.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static RestfulServer servlet;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
public void beforeEach() {
|
||||
ourGenerateOperationOutcome = false;
|
||||
ourExceptionType=null;
|
||||
|
||||
myInterceptor = new ExceptionHandlingInterceptor();
|
||||
myInterceptor.setReturnStackTracesForExceptionTypes(Throwable.class);
|
||||
ourServer.registerInterceptor(myInterceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalError() throws Exception {
|
||||
myInterceptor.setReturnStackTracesForExceptionTypes(Throwable.class);
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient?throwInternalError=aaa");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
|
||||
OperationOutcome oo = (OperationOutcome) ourCtx.newXmlParser().parseResource(responseContent);
|
||||
assertThat(oo.getIssueFirstRep().getDiagnosticsElement().getValue(), StringContains.containsString("Exception Text"));
|
||||
assertThat(oo.getIssueFirstRep().getDiagnosticsElement().getValue(), (StringContains.containsString("InternalErrorException: Exception Text")));
|
||||
}
|
||||
|
@ -86,36 +95,34 @@ public class ExceptionHandlingInterceptorTest {
|
|||
|
||||
//Given: We have an interceptor which causes a failure after the response output stream has been started.
|
||||
ProblemGeneratingInterceptor interceptor = new ProblemGeneratingInterceptor();
|
||||
servlet.registerInterceptor(interceptor);
|
||||
myInterceptor.setReturnStackTracesForExceptionTypes(Throwable.class);
|
||||
ourServer.registerInterceptor(interceptor);
|
||||
|
||||
//When: We make a request to the server, triggering this exception to be thrown on an otherwise successful request
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?succeed=true");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient?succeed=true");
|
||||
httpGet.setHeader("Accept-encoding", "gzip");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
servlet.unregisterInterceptor(interceptor);
|
||||
ourServer.unregisterInterceptor(interceptor);
|
||||
|
||||
//Then: This should still return an OperationOutcome, and not explode with an HTML IllegalState response.
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
|
||||
ourLog.info(servlet.getFhirContext().newXmlParser().encodeResourceToString(oo));
|
||||
OperationOutcome oo = (OperationOutcome) ourCtx.newXmlParser().parseResource(responseContent);
|
||||
ourLog.info(ourCtx.newXmlParser().encodeResourceToString(oo));
|
||||
assertThat(oo.getIssueFirstRep().getDiagnosticsElement().getValue(), StringContains.containsString("Simulated IOException"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInternalErrorFormatted() throws Exception {
|
||||
myInterceptor.setReturnStackTracesForExceptionTypes(Throwable.class);
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?throwInternalError=aaa&_format=true");
|
||||
HttpGet httpGet = new HttpGet(ourServer.getBaseUrl() + "/Patient?throwInternalError=aaa&_format=true");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = (OperationOutcome) servlet.getFhirContext().newXmlParser().parseResource(responseContent);
|
||||
OperationOutcome oo = (OperationOutcome) ourCtx.newXmlParser().parseResource(responseContent);
|
||||
assertThat(oo.getIssueFirstRep().getDiagnosticsElement().getValue(), StringContains.containsString("Exception Text"));
|
||||
assertThat(oo.getIssueFirstRep().getDiagnosticsElement().getValue(), (StringContains.containsString("InternalErrorException: Exception Text")));
|
||||
}
|
||||
|
@ -125,33 +132,8 @@ public class ExceptionHandlingInterceptorTest {
|
|||
|
||||
@AfterAll
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
JettyUtil.closeServer(ourServer);
|
||||
TestUtil.randomizeLocaleAndTimezone();
|
||||
}
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
ourServer = new Server(0);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
servlet = new RestfulServer(ourCtx);
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourServer);
|
||||
ourPort = JettyUtil.getPortForStartedServer(ourServer);
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
myInterceptor = new ExceptionHandlingInterceptor();
|
||||
servlet.registerInterceptor(myInterceptor);
|
||||
}
|
||||
|
||||
public static class ProblemGeneratingInterceptor {
|
||||
@Hook(Pointcut.SERVER_OUTGOING_WRITER_CREATED)
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
|
|||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||
|
@ -45,12 +46,12 @@ public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServ
|
|||
private FhirContext myFhirContext;
|
||||
private List<Object> myProviders = new ArrayList<>();
|
||||
private FhirVersionEnum myFhirVersion;
|
||||
private IGenericClient myFhirClient;
|
||||
private RestfulServer myServlet;
|
||||
private List<Consumer<RestfulServer>> myConsumers = new ArrayList<>();
|
||||
private Map<String, Object> myRunningServerUserData = new HashMap<>();
|
||||
private ServerValidationModeEnum myServerValidationMode = ServerValidationModeEnum.NEVER;
|
||||
private IPagingProvider myPagingProvider;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -83,7 +84,6 @@ public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServ
|
|||
|
||||
myFhirContext.getRestfulClientFactory().setSocketTimeout((int) (500 * DateUtils.MILLIS_PER_SECOND));
|
||||
myFhirContext.getRestfulClientFactory().setServerValidationMode(myServerValidationMode);
|
||||
myFhirClient = myFhirContext.newRestfulGenericClient(getBaseUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,7 +110,6 @@ public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServ
|
|||
if (!isRunning()) {
|
||||
return;
|
||||
}
|
||||
myFhirClient = null;
|
||||
myRunningServerUserData.clear();
|
||||
myPagingProvider = null;
|
||||
myServlet = null;
|
||||
|
@ -123,8 +122,11 @@ public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServ
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new client for each callof this method
|
||||
*/
|
||||
public IGenericClient getFhirClient() {
|
||||
return myFhirClient;
|
||||
return myFhirContext.newRestfulGenericClient(getBaseUrl());
|
||||
}
|
||||
|
||||
public FhirContext getFhirContext() {
|
||||
|
@ -210,4 +212,8 @@ public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServ
|
|||
public RestfulServerExtension registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor) {
|
||||
return withServer(t -> t.getInterceptorService().registerAnonymousInterceptor(thePointcut, theInterceptor));
|
||||
}
|
||||
|
||||
public RestfulServerExtension withDefaultResponseEncoding(EncodingEnum theEncodingEnum) {
|
||||
return withServer(t -> t.setDefaultResponseEncoding(theEncodingEnum));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue