Allow interceptors to modify request contents

This commit is contained in:
James Agnew 2018-01-06 14:27:32 -05:00
parent 0369b7fd70
commit 377bae8c16
7 changed files with 278 additions and 357 deletions

View File

@ -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<Immunization> myImmunizationDao;
@Autowired
@Qualifier("myLocationDaoR4")
private IFhirResourceDao<Location> myLocationDao;
@Autowired
@Qualifier("myMediaDaoR4")
private IFhirResourceDao<Media> myMediaDao;
@Autowired
@Qualifier("myMedicationDaoR4")
private IFhirResourceDao<Medication> myMedicationDao;
@Autowired
@Qualifier("myMedicationRequestDaoR4")
private IFhirResourceDao<MedicationRequest> myMedicationRequestDao;
@Autowired
@Qualifier("myNamingSystemDaoR4")
private IFhirResourceDao<NamingSystem> myNamingSystemDao;
@Autowired
@Qualifier("myObservationDaoR4")
private IFhirResourceDao<Observation> myObservationDao;
@Autowired
@Qualifier("myOperationDefinitionDaoR4")
private IFhirResourceDao<OperationDefinition> myOperationDefinitionDao;
@Autowired
@Qualifier("myOrganizationDaoR4")
private IFhirResourceDao<Organization> myOrganizationDao;
@Autowired
@Qualifier("myPatientDaoR4")
private IFhirResourceDaoPatient<Patient> myPatientDao;
@Autowired
@Qualifier("myPractitionerDaoR4")
private IFhirResourceDao<Practitioner> myPractitionerDao;
@Autowired
@Qualifier("myQuestionnaireDaoR4")
private IFhirResourceDao<Questionnaire> myQuestionnaireDao;
@Autowired
@Qualifier("myQuestionnaireResponseDaoR4")
private IFhirResourceDao<QuestionnaireResponse> myQuestionnaireResponseDao;
@Autowired
@Qualifier("myResourceProvidersR4")
private Object myResourceProviders;
@Autowired
@Qualifier("myStructureDefinitionDaoR4")
private IFhirResourceDao<StructureDefinition> myStructureDefinitionDao;
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
@Autowired
@Qualifier("mySubstanceDaoR4")
private IFhirResourceDao<Substance> mySubstanceDao;
@Autowired
@Qualifier("mySystemDaoR4")
private IFhirSystemDao<Bundle, Meta> 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();

View File

@ -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.
* <p>
* 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
* </p>
*/
public void setRequestContents(byte[] theRequestContents) {
myRequestContents = theRequestContents;
}
private class RequestOperationCallback implements IRequestOperationCallback {
private List<IServerInterceptor> getInterceptors() {

View File

@ -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<T> {
@ -460,14 +468,7 @@ public abstract class BaseMethodBinding<T> {
}
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<? extends IBaseResource>) 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<T> {
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<T> {
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<T> {
}
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;
}
}

View File

@ -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);

View File

@ -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<RestOperationTypeEnum> opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
ArgumentCaptor<ActionRequestDetails> arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<IBaseResource> 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<RestOperationTypeEnum> opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
ArgumentCaptor<ActionRequestDetails> arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<RequestDetails> rdCapt = ArgumentCaptor.forClass(RequestDetails.class);
ArgumentCaptor<IBaseResource> 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<RestOperationTypeEnum> opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
ArgumentCaptor<ActionRequestDetails> arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<RequestDetails> rdCapt = ArgumentCaptor.forClass(RequestDetails.class);
ArgumentCaptor<IBaseResource> 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<RestOperationTypeEnum> opTypeCapt = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
ArgumentCaptor<ActionRequestDetails> arTypeCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
ArgumentCaptor<IBaseResource> 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<Patient> getResourceType() {
return Patient.class;
@ -236,11 +257,6 @@ public class InterceptorDstu3Test {
return new MethodOutcome();
}
@Create()
public MethodOutcome create(@ResourceParam Patient theResource) {
return new MethodOutcome();
}
}
}

View File

@ -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("<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\""));
assertThat(responseContent, containsString("Failed to parse request body as XML resource. Error was: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'F'"));
}
@Test
public void testCreateWithIncorrectContent1() throws Exception {
@ -169,6 +168,28 @@ public class CreateR4Test {
}
/**
* #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("<OperationOutcome xmlns=\"http://hl7.org/fhir\"><issue><severity value=\"error\"/><code value=\"processing\"/><diagnostics value=\""));
assertThat(responseContent, containsString("Failed to parse request body as XML resource. Error was: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'F'"));
}
@Test
public void testSearch() throws Exception {
@ -184,14 +205,14 @@ public class CreateR4Test {
//@formatter:off
assertThat(responseContent, stringContainsInOrder(
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<id value=\"0\"/>",
"<meta>",
"<profile value=\"http://example.com/StructureDefinition/patient_with_extensions\"/>",
"</meta>",
"<modifierExtension url=\"http://example.com/ext/date\">",
"<valueDate value=\"2011-01-01\"/>",
"</modifierExtension>",
"<Patient xmlns=\"http://hl7.org/fhir\">",
"<id value=\"0\"/>",
"<meta>",
"<profile value=\"http://example.com/StructureDefinition/patient_with_extensions\"/>",
"</meta>",
"<modifierExtension url=\"http://example.com/ext/date\">",
"<valueDate value=\"2011-01-01\"/>",
"</modifierExtension>",
"</Patient>"));
//@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);
}

View File

@ -62,6 +62,12 @@
can be useful in order to allow interceptors to
change payload contents being saved.
</action>
<action type="add">
A new method has been added to RequestDetails called
<![CDATA[<code>setRequestContents()]]> which can be used
by interceptors to modify the request body before it
is parsed by the server.
</action>
</release>
<release version="3.1.0" date="2017-11-23">
<action type="add">