From 377bae8c16d57d6358df3adc1d809361813934d5 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 6 Jan 2018 14:27:32 -0500 Subject: [PATCH] Allow interceptors to modify request contents --- ...urceDaoR4SearchWithLuceneDisabledTest.java | 63 +---- .../fhir/rest/api/server/RequestDetails.java | 17 ++ .../rest/server/method/BaseMethodBinding.java | 110 +++----- .../server/servlet/ServletRequestDetails.java | 67 ++--- .../rest/server/InterceptorDstu3Test.java | 240 ++++++++++-------- .../ca/uhn/fhir/rest/server/CreateR4Test.java | 132 ++++++---- src/changes/changes.xml | 6 + 7 files changed, 278 insertions(+), 357 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java index 58b6ec334cf..27edd5cd5e1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithLuceneDisabledTest.java @@ -27,9 +27,6 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; -// @RunWith(SpringJUnit4ClassRunner.class) -// @ContextConfiguration(classes= {TestR4WithoutLuceneConfig.class}) -// @SuppressWarnings("unchecked") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { TestR4WithoutLuceneConfig.class }) public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { @@ -79,62 +76,8 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { @Autowired private FhirContext myFhirCtx; @Autowired - @Qualifier("myImmunizationDaoR4") - private IFhirResourceDao myImmunizationDao; - @Autowired - @Qualifier("myLocationDaoR4") - private IFhirResourceDao myLocationDao; - @Autowired - @Qualifier("myMediaDaoR4") - private IFhirResourceDao myMediaDao; - @Autowired - @Qualifier("myMedicationDaoR4") - private IFhirResourceDao myMedicationDao; - @Autowired - @Qualifier("myMedicationRequestDaoR4") - private IFhirResourceDao myMedicationRequestDao; - @Autowired - @Qualifier("myNamingSystemDaoR4") - private IFhirResourceDao myNamingSystemDao; - @Autowired - @Qualifier("myObservationDaoR4") - private IFhirResourceDao myObservationDao; - @Autowired - @Qualifier("myOperationDefinitionDaoR4") - private IFhirResourceDao myOperationDefinitionDao; - @Autowired @Qualifier("myOrganizationDaoR4") private IFhirResourceDao myOrganizationDao; - @Autowired - @Qualifier("myPatientDaoR4") - private IFhirResourceDaoPatient myPatientDao; - @Autowired - @Qualifier("myPractitionerDaoR4") - private IFhirResourceDao myPractitionerDao; - @Autowired - @Qualifier("myQuestionnaireDaoR4") - private IFhirResourceDao myQuestionnaireDao; - @Autowired - @Qualifier("myQuestionnaireResponseDaoR4") - private IFhirResourceDao myQuestionnaireResponseDao; - @Autowired - @Qualifier("myResourceProvidersR4") - private Object myResourceProviders; - @Autowired - @Qualifier("myStructureDefinitionDaoR4") - private IFhirResourceDao myStructureDefinitionDao; - @Autowired - @Qualifier("mySubscriptionDaoR4") - private IFhirResourceDaoSubscription mySubscriptionDao; - @Autowired - @Qualifier("mySubstanceDaoR4") - private IFhirResourceDao mySubstanceDao; - @Autowired - @Qualifier("mySystemDaoR4") - private IFhirSystemDao mySystemDao; - @Autowired - @Qualifier("mySystemProviderR4") - private JpaSystemProviderR4 mySystemProvider; @Autowired protected PlatformTransactionManager myTxManager; @@ -169,7 +112,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { } @Test - public void testSearchWithRegularParam() throws Exception { + public void testSearchWithRegularParam() { String methodName = "testEverythingIncludesBackReferences"; Organization org = new Organization(); @@ -183,7 +126,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { } @Test - public void testSearchWithContent() throws Exception { + public void testSearchWithContent() { String methodName = "testEverythingIncludesBackReferences"; Organization org = new Organization(); @@ -201,7 +144,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { } @Test - public void testSearchWithText() throws Exception { + public void testSearchWithText() { String methodName = "testEverythingIncludesBackReferences"; Organization org = new Organization(); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java index 3949b409c01..51aaa849982 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java @@ -10,6 +10,8 @@ import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.Reader; @@ -348,6 +350,21 @@ public abstract class RequestDetails { return myRequestContents; } + /** + * This method may be used to modify the contents of the incoming + * request by hardcoding a value which will be used instead of the + * value received by the client. + *

+ * This method is useful for modifying the request body prior + * to parsing within interceptors. It generally only has an + * impact when called in the {@link IServerInterceptor#incomingRequestPostProcessed(RequestDetails, HttpServletRequest, HttpServletResponse)} + * method + *

+ */ + public void setRequestContents(byte[] theRequestContents) { + myRequestContents = theRequestContents; + } + private class RequestOperationCallback implements IRequestOperationCallback { private List getInterceptors() { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java index 64658656a1c..151c0cab333 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java @@ -19,32 +19,40 @@ package ca.uhn.fhir.rest.server.method; * limitations under the License. * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; - -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.*; +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.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.IRestfulServer; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; +import ca.uhn.fhir.rest.server.BundleProviders; +import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.exceptions.*; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.util.ReflectionUtil; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.*; -import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; -import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.ReflectionUtil; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isBlank; public abstract class BaseMethodBinding { @@ -460,14 +468,7 @@ public abstract class BaseMethodBinding { } returnType = returnTypeFromAnnotation; } else { - // if (IRestfulClient.class.isAssignableFrom(theMethod.getDeclaringClass())) { - // Clients don't define their methods in resource specific types, so they can - // infer their resource type from the method return type. returnType = (Class) returnTypeFromMethod; - // } else { - // This is a plain provider method returning a resource, so it should be - // an operation or global search presumably - // returnType = null; } } @@ -505,26 +506,6 @@ public abstract class BaseMethodBinding { throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); } - // // each operation name must have a request type annotation and be - // unique - // if (null != read) { - // return rm; - // } - // - // SearchMethodBinding sm = new SearchMethodBinding(); - // if (null != search) { - // sm.setRequestType(SearchMethodBinding.RequestType.GET); - // } else if (null != theMethod.getAnnotation(PUT.class)) { - // sm.setRequestType(SearchMethodBinding.RequestType.PUT); - // } else if (null != theMethod.getAnnotation(POST.class)) { - // sm.setRequestType(SearchMethodBinding.RequestType.POST); - // } else if (null != theMethod.getAnnotation(DELETE.class)) { - // sm.setRequestType(SearchMethodBinding.RequestType.DELETE); - // } else { - // return null; - // } - // - // return sm; } private static boolean isResourceInterface(Class theReturnTypeFromMethod) { @@ -555,8 +536,6 @@ public abstract class BaseMethodBinding { return false; } return true; - // boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false; - // return retVal; } public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) { @@ -574,39 +553,8 @@ public abstract class BaseMethodBinding { } if (obj1 == null) { return false; - // throw new ConfigurationException("Method '" + - // theNextMethod.getName() + "' on type '" + - // theNextMethod.getDeclaringClass().getSimpleName() + - // " has no FHIR method annotations."); } return true; } - /** - * @see ServletRequestDetails#getByteStreamRequestContents() - */ - public static class ActiveRequestReader implements IRequestReader { - @Override - public InputStream getInputStream(RequestDetails theRequestDetails) throws IOException { - return theRequestDetails.getInputStream(); - } - } - - /** - * @see ServletRequestDetails#getByteStreamRequestContents() - */ - public static class InactiveRequestReader implements IRequestReader { - @Override - public InputStream getInputStream(RequestDetails theRequestDetails) { - throw new IllegalStateException("The servlet-api JAR is not found on the classpath. Please check that this library is available."); - } - } - - /** - * @see ServletRequestDetails#getByteStreamRequestContents() - */ - public static interface IRequestReader { - InputStream getInputStream(RequestDetails theRequestDetails) throws IOException; - } - } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java index f870dd6289c..cb1c3d96451 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java @@ -19,38 +19,35 @@ package ca.uhn.fhir.rest.server.servlet; * limitations under the License. * #L% */ -import static org.apache.commons.lang3.StringUtils.isNotBlank; -import java.io.*; -import java.nio.charset.Charset; -import java.util.*; -import java.util.zip.GZIPInputStream; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.IOUtils; - -import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.method.BaseMethodBinding; -import ca.uhn.fhir.rest.server.method.BaseMethodBinding.IRequestReader; +import org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.GZIPInputStream; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class ServletRequestDetails extends RequestDetails { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class); - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static volatile IRequestReader ourRequestReader; + private RestfulServer myServer; private HttpServletRequest myServletRequest; private HttpServletResponse myServletResponse; - private byte[] requestContents; public ServletRequestDetails() { super(); @@ -59,36 +56,9 @@ public class ServletRequestDetails extends RequestDetails { @Override protected byte[] getByteStreamRequestContents() { - /* - * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on - * servlet-api in clients since there is no point. So we dynamically load a class that does the servlet processing - * in servers. Down the road it may make sense to just split the method binding classes into server and client - * versions, but this isn't actually a huge deal I don't think. - */ - IRequestReader reader = ourRequestReader; - if (reader == null) { - try { - Class.forName("javax.servlet.ServletInputStream"); - String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } catch (ClassNotFoundException e) { - String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } - ourRequestReader = reader; - } - try { - InputStream inputStream = reader.getInputStream(this); - requestContents = IOUtils.toByteArray(inputStream); + InputStream inputStream = getInputStream(); + byte[] requestContents = IOUtils.toByteArray(inputStream); if (myServer.isUncompressIncomingContents()) { String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING); @@ -100,7 +70,6 @@ public class ServletRequestDetails extends RequestDetails { } } } - // FIXME resource leak return requestContents; } catch (IOException e) { ourLog.error("Could not load request resource", e); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java index 11df89bfa80..9bad2f73053 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/InterceptorDstu3Test.java @@ -1,20 +1,22 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Validate; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; +import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; @@ -26,6 +28,7 @@ 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.hamcrest.core.StringContains; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -33,18 +36,14 @@ import org.junit.*; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Validate; -import ca.uhn.fhir.rest.api.*; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; -import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; -import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; public class InterceptorDstu3Test { @@ -53,6 +52,7 @@ public class InterceptorDstu3Test { private static int ourPort; private static Server ourServer; private static RestfulServer ourServlet; + private static Patient ourLastPatient; private IServerInterceptor myInterceptor1; private IServerInterceptor myInterceptor2; @@ -69,79 +69,18 @@ public class InterceptorDstu3Test { myInterceptor2 = mock(IServerInterceptor.class); } - @SuppressWarnings("deprecation") - @Test - public void testServerOperationInterceptorAdapterMethods() { - ServerOperationInterceptorAdapter i = new ServerOperationInterceptorAdapter(); - i.resourceCreated(null, null); - i.resourceDeleted(null, null); - i.resourceUpdated(null, null); - i.resourceUpdated(null, null, null); + private String createInput() { + return "{\n" + + " \"resourceType\":\"Patient\",\n" + + " \"id\":\"1855669\",\n" + + " \"meta\":{\n" + + " \"versionId\":\"1\",\n" + + " \"lastUpdated\":\"2016-02-18T07:41:35.953-05:00\"\n" + + " },\n" + + " \"active\":true\n" + + "}"; } - @Test - public void testResponseWithOperationOutcome() throws Exception { - ourServlet.setInterceptors(myInterceptor1); - - when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); - - String input = createInput(); - - HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate"); - httpPost.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8"))); - HttpResponse status = ourClient.execute(httpPost); - IOUtils.closeQuietly(status.getEntity().getContent()); - - InOrder order = inOrder(myInterceptor1); - order.verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); - order.verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - ArgumentCaptor opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - ArgumentCaptor arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor resourceCapt = ArgumentCaptor.forClass(IBaseResource.class); - order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture()); - order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), resourceCapt.capture()); - - assertEquals(1, resourceCapt.getAllValues().size()); - assertEquals(OperationOutcome.class, resourceCapt.getAllValues().get(0).getClass()); - } - - @Test - public void testResponseWithNothing() throws Exception { - ourServlet.setInterceptors(myInterceptor1); - - when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); - - String input = createInput(); - - HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); - httpPost.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8"))); - HttpResponse status = ourClient.execute(httpPost); - try { - assertEquals(201, status.getStatusLine().getStatusCode()); - } finally { - IOUtils.closeQuietly(status.getEntity().getContent()); - } - - InOrder order = inOrder(myInterceptor1); - verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); - verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - ArgumentCaptor opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class); - ArgumentCaptor arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor rdCapt = ArgumentCaptor.forClass(RequestDetails.class); - ArgumentCaptor resourceCapt = ArgumentCaptor.forClass(IBaseResource.class); - verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture()); - verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), resourceCapt.capture()); - - assertEquals(1, resourceCapt.getAllValues().size()); - assertEquals(null, resourceCapt.getAllValues().get(0)); -// assertEquals("", rdCapt.getAllValues().get(0).get) - } - - @Test public void testResourceResponseIncluded() throws Exception { ourServlet.setInterceptors(myInterceptor1, myInterceptor2); @@ -184,16 +123,91 @@ public class InterceptorDstu3Test { assertNotNull(arTypeCapt.getValue().getResource()); } - private String createInput() { - return "{\n" + - " \"resourceType\":\"Patient\",\n" + - " \"id\":\"1855669\",\n" + - " \"meta\":{\n" + - " \"versionId\":\"1\",\n" + - " \"lastUpdated\":\"2016-02-18T07:41:35.953-05:00\"\n" + - " },\n" + - " \"active\":true\n" + - "}"; + public void testModifyResponse() { + InterceptorAdapter interceptor = new InterceptorAdapter(){ + @Override + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException { + ServletRequestDetails srd = (ServletRequestDetails)theRequestDetails; + String input = new String(srd.loadRequestContents(), Constants.CHARSET_UTF8); + assertThat(input, StringContains.containsString("\"active\":true")); + + String newInput = createInput().replace("true", "false"); + srd.setRequestContents(newInput.getBytes(Constants.CHARSET_UTF8)); + return true; + } + }; + } + + @Test + public void testResponseWithNothing() throws Exception { + ourServlet.setInterceptors(myInterceptor1); + + when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); + + String input = createInput(); + + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8"))); + HttpResponse status = ourClient.execute(httpPost); + try { + assertEquals(201, status.getStatusLine().getStatusCode()); + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + + InOrder order = inOrder(myInterceptor1); + verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); + verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + ArgumentCaptor opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + ArgumentCaptor arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor rdCapt = ArgumentCaptor.forClass(RequestDetails.class); + ArgumentCaptor resourceCapt = ArgumentCaptor.forClass(IBaseResource.class); + verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture()); + verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), resourceCapt.capture()); + + assertEquals(1, resourceCapt.getAllValues().size()); + assertEquals(null, resourceCapt.getAllValues().get(0)); +// assertEquals("", rdCapt.getAllValues().get(0).get) + } + + @Test + public void testResponseWithOperationOutcome() throws Exception { + ourServlet.setInterceptors(myInterceptor1); + + when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); + + String input = createInput(); + + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate"); + httpPost.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8"))); + HttpResponse status = ourClient.execute(httpPost); + IOUtils.closeQuietly(status.getEntity().getContent()); + + InOrder order = inOrder(myInterceptor1); + order.verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + ArgumentCaptor opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + ArgumentCaptor arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor resourceCapt = ArgumentCaptor.forClass(IBaseResource.class); + order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture()); + order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), resourceCapt.capture()); + + assertEquals(1, resourceCapt.getAllValues().size()); + assertEquals(OperationOutcome.class, resourceCapt.getAllValues().get(0).getClass()); + } + + @SuppressWarnings("deprecation") + @Test + public void testServerOperationInterceptorAdapterMethods() { + ServerOperationInterceptorAdapter i = new ServerOperationInterceptorAdapter(); + i.resourceCreated(null, null); + i.resourceDeleted(null, null); + i.resourceUpdated(null, null); + i.resourceUpdated(null, null, null); } @AfterClass @@ -226,6 +240,13 @@ public class InterceptorDstu3Test { public static class DummyPatientResourceProvider implements IResourceProvider { + + @Create() + public MethodOutcome create(@ResourceParam Patient theResource) { + ourLastPatient = theResource; + return new MethodOutcome(); + } + @Override public Class getResourceType() { return Patient.class; @@ -236,11 +257,6 @@ public class InterceptorDstu3Test { return new MethodOutcome(); } - @Create() - public MethodOutcome create(@ResourceParam Patient theResource) { - return new MethodOutcome(); - } - } } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/CreateR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/CreateR4Test.java index e2a39db2802..5a03adf5002 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/CreateR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/CreateR4Test.java @@ -1,16 +1,11 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.MyPatientWithExtensions; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -25,31 +20,57 @@ import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent; -import org.junit.*; +import org.hl7.fhir.r4.model.Patient; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.client.MyPatientWithExtensions; -import ca.uhn.fhir.util.PortUtil; -import ca.uhn.fhir.util.TestUtil; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class CreateR4Test { - private static CloseableHttpClient ourClient; - - private static FhirContext ourCtx = FhirContext.forR4(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateR4Test.class); + public static IBaseOperationOutcome ourReturnOo; + private static CloseableHttpClient ourClient; + private static FhirContext ourCtx = FhirContext.forR4(); private static int ourPort; private static Server ourServer; - public static IBaseOperationOutcome ourReturnOo; @Before public void before() { ourReturnOo = null; } - + + @Test + public void testCreateIgnoresIdInResourceBody() throws Exception { + + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.setEntity(new StringEntity("{\"resourceType\":\"Patient\", \"id\":\"999\", \"status\":\"active\"}", ContentType.parse("application/fhir+json; charset=utf-8"))); + HttpResponse status = ourClient.execute(httpPost); + + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(status.getEntity().getContent()); + + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(201, status.getStatusLine().getStatusCode()); + + assertEquals(1, status.getHeaders("Location").length); + assertEquals(0, status.getHeaders("Content-Location").length); + assertEquals("http://localhost:" + ourPort + "/Patient/1", status.getFirstHeader("Location").getValue()); + + } + /** * #472 */ @@ -76,7 +97,7 @@ public class CreateR4Test { @Test public void testCreateReturnsOperationOutcome() throws Exception { ourReturnOo = new OperationOutcome().addIssue(new OperationOutcomeIssueComponent().setDiagnostics("DIAG")); - + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); httpPost.setEntity(new StringEntity("{\"resourceType\":\"Patient\", \"status\":\"active\"}", ContentType.parse("application/fhir+json; charset=utf-8"))); HttpResponse status = ourClient.execute(httpPost); @@ -91,28 +112,6 @@ public class CreateR4Test { assertThat(responseContent, containsString("DIAG")); } - /** - * #342 - */ - @Test - public void testCreateWithInvalidContent() throws Exception { - - HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); - httpPost.setEntity(new StringEntity("FOO", ContentType.parse("application/xml+fhir; charset=utf-8"))); - HttpResponse status = ourClient.execute(httpPost); - - String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); - IOUtils.closeQuietly(status.getEntity().getContent()); - - ourLog.info("Response was:\n{}", responseContent); - - assertEquals(400, status.getStatusLine().getStatusCode()); - - assertThat(responseContent, containsString("", - "", - "", - "", - "", - "", - "", - "", + "", + "", + "", + "", + "", + "", + "", + "", "")); //@formatter:on @@ -231,6 +252,7 @@ public class CreateR4Test { @Create() public MethodOutcome create(@ResourceParam Patient theIdParam) { + assertNull(theIdParam.getIdElement().getIdPart()); return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo); } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9c0e88f7246..883a4bf9917 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -62,6 +62,12 @@ can be useful in order to allow interceptors to change payload contents being saved. + + A new method has been added to RequestDetails called + setRequestContents()]]> which can be used + by interceptors to modify the request body before it + is parsed by the server. +