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.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
// @RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration(classes= {TestR4WithoutLuceneConfig.class})
// @SuppressWarnings("unchecked")
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestR4WithoutLuceneConfig.class }) @ContextConfiguration(classes = { TestR4WithoutLuceneConfig.class })
public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest { public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
@ -79,62 +76,8 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
@Autowired @Autowired
private FhirContext myFhirCtx; private FhirContext myFhirCtx;
@Autowired @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") @Qualifier("myOrganizationDaoR4")
private IFhirResourceDao<Organization> myOrganizationDao; 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 @Autowired
protected PlatformTransactionManager myTxManager; protected PlatformTransactionManager myTxManager;
@ -169,7 +112,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
} }
@Test @Test
public void testSearchWithRegularParam() throws Exception { public void testSearchWithRegularParam() {
String methodName = "testEverythingIncludesBackReferences"; String methodName = "testEverythingIncludesBackReferences";
Organization org = new Organization(); Organization org = new Organization();
@ -183,7 +126,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
} }
@Test @Test
public void testSearchWithContent() throws Exception { public void testSearchWithContent() {
String methodName = "testEverythingIncludesBackReferences"; String methodName = "testEverythingIncludesBackReferences";
Organization org = new Organization(); Organization org = new Organization();
@ -201,7 +144,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
} }
@Test @Test
public void testSearchWithText() throws Exception { public void testSearchWithText() {
String methodName = "testEverythingIncludesBackReferences"; String methodName = "testEverythingIncludesBackReferences";
Organization org = new Organization(); 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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
@ -348,6 +350,21 @@ public abstract class RequestDetails {
return myRequestContents; 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 class RequestOperationCallback implements IRequestOperationCallback {
private List<IServerInterceptor> getInterceptors() { private List<IServerInterceptor> getInterceptors() {

View File

@ -19,32 +19,40 @@ package ca.uhn.fhir.rest.server.method;
* limitations under the License. * limitations under the License.
* #L% * #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.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException; import java.io.IOException;
import ca.uhn.fhir.context.FhirContext; import java.io.Reader;
import ca.uhn.fhir.model.api.*; import java.lang.reflect.InvocationTargetException;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import java.lang.reflect.Method;
import ca.uhn.fhir.parser.IParser; import java.util.*;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.*; import static org.apache.commons.lang3.StringUtils.isBlank;
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;
public abstract class BaseMethodBinding<T> { public abstract class BaseMethodBinding<T> {
@ -460,14 +468,7 @@ public abstract class BaseMethodBinding<T> {
} }
returnType = returnTypeFromAnnotation; returnType = returnTypeFromAnnotation;
} else { } 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; 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()); 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) { private static boolean isResourceInterface(Class<?> theReturnTypeFromMethod) {
@ -555,8 +536,6 @@ public abstract class BaseMethodBinding<T> {
return false; return false;
} }
return true; return true;
// boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
// return retVal;
} }
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) { public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
@ -574,39 +553,8 @@ public abstract class BaseMethodBinding<T> {
} }
if (obj1 == null) { if (obj1 == null) {
return false; return false;
// throw new ConfigurationException("Method '" +
// theNextMethod.getName() + "' on type '" +
// theNextMethod.getDeclaringClass().getSimpleName() +
// " has no FHIR method annotations.");
} }
return true; 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. * limitations under the License.
* #L% * #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.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding.IRequestReader;
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 { public class ServletRequestDetails extends RequestDetails {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class); 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 RestfulServer myServer;
private HttpServletRequest myServletRequest; private HttpServletRequest myServletRequest;
private HttpServletResponse myServletResponse; private HttpServletResponse myServletResponse;
private byte[] requestContents;
public ServletRequestDetails() { public ServletRequestDetails() {
super(); super();
@ -59,36 +56,9 @@ public class ServletRequestDetails extends RequestDetails {
@Override @Override
protected byte[] getByteStreamRequestContents() { 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 { try {
Class.forName("javax.servlet.ServletInputStream"); InputStream inputStream = getInputStream();
String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; byte[] requestContents = IOUtils.toByteArray(inputStream);
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);
if (myServer.isUncompressIncomingContents()) { if (myServer.isUncompressIncomingContents()) {
String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING); String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING);
@ -100,7 +70,6 @@ public class ServletRequestDetails extends RequestDetails {
} }
} }
} }
// FIXME resource leak
return requestContents; return requestContents;
} catch (IOException e) { } catch (IOException e) {
ourLog.error("Could not load request resource", e); ourLog.error("Could not load request resource", e);

View File

@ -1,20 +1,22 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.MatcherAssert.assertThat; import ca.uhn.fhir.context.FhirContext;
import static org.hamcrest.Matchers.contains; import ca.uhn.fhir.model.api.IResource;
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.rest.annotation.Create; 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.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost; 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.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -33,18 +36,14 @@ import org.junit.*;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InOrder; import org.mockito.InOrder;
import ca.uhn.fhir.context.FhirContext; import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.model.api.IResource; import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.annotation.ResourceParam; import java.util.ArrayList;
import ca.uhn.fhir.rest.annotation.Validate; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails; import static org.junit.Assert.*;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import static org.mockito.Matchers.any;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import static org.mockito.Mockito.*;
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;
public class InterceptorDstu3Test { public class InterceptorDstu3Test {
@ -53,6 +52,7 @@ public class InterceptorDstu3Test {
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
private static RestfulServer ourServlet; private static RestfulServer ourServlet;
private static Patient ourLastPatient;
private IServerInterceptor myInterceptor1; private IServerInterceptor myInterceptor1;
private IServerInterceptor myInterceptor2; private IServerInterceptor myInterceptor2;
@ -69,79 +69,18 @@ public class InterceptorDstu3Test {
myInterceptor2 = mock(IServerInterceptor.class); myInterceptor2 = mock(IServerInterceptor.class);
} }
@SuppressWarnings("deprecation") private String createInput() {
@Test return "{\n" +
public void testServerOperationInterceptorAdapterMethods() { " \"resourceType\":\"Patient\",\n" +
ServerOperationInterceptorAdapter i = new ServerOperationInterceptorAdapter(); " \"id\":\"1855669\",\n" +
i.resourceCreated(null, null); " \"meta\":{\n" +
i.resourceDeleted(null, null); " \"versionId\":\"1\",\n" +
i.resourceUpdated(null, null); " \"lastUpdated\":\"2016-02-18T07:41:35.953-05:00\"\n" +
i.resourceUpdated(null, null, null); " },\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 @Test
public void testResourceResponseIncluded() throws Exception { public void testResourceResponseIncluded() throws Exception {
ourServlet.setInterceptors(myInterceptor1, myInterceptor2); ourServlet.setInterceptors(myInterceptor1, myInterceptor2);
@ -184,16 +123,91 @@ public class InterceptorDstu3Test {
assertNotNull(arTypeCapt.getValue().getResource()); assertNotNull(arTypeCapt.getValue().getResource());
} }
private String createInput() { public void testModifyResponse() {
return "{\n" + InterceptorAdapter interceptor = new InterceptorAdapter(){
" \"resourceType\":\"Patient\",\n" + @Override
" \"id\":\"1855669\",\n" + public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
" \"meta\":{\n" + ServletRequestDetails srd = (ServletRequestDetails)theRequestDetails;
" \"versionId\":\"1\",\n" + String input = new String(srd.loadRequestContents(), Constants.CHARSET_UTF8);
" \"lastUpdated\":\"2016-02-18T07:41:35.953-05:00\"\n" + assertThat(input, StringContains.containsString("\"active\":true"));
" },\n" +
" \"active\":true\n" + 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 @AfterClass
@ -226,6 +240,13 @@ public class InterceptorDstu3Test {
public static class DummyPatientResourceProvider implements IResourceProvider { public static class DummyPatientResourceProvider implements IResourceProvider {
@Create()
public MethodOutcome create(@ResourceParam Patient theResource) {
ourLastPatient = theResource;
return new MethodOutcome();
}
@Override @Override
public Class<Patient> getResourceType() { public Class<Patient> getResourceType() {
return Patient.class; return Patient.class;
@ -236,11 +257,6 @@ public class InterceptorDstu3Test {
return new MethodOutcome(); 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; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString; import ca.uhn.fhir.context.FhirContext;
import static org.hamcrest.Matchers.not; import ca.uhn.fhir.rest.annotation.*;
import static org.hamcrest.Matchers.stringContainsInOrder; import ca.uhn.fhir.rest.api.MethodOutcome;
import static org.junit.Assert.assertEquals; import ca.uhn.fhir.rest.client.MyPatientWithExtensions;
import static org.junit.Assert.assertThat; 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 org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; 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.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.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 java.nio.charset.StandardCharsets;
import ca.uhn.fhir.rest.annotation.*; import java.util.ArrayList;
import ca.uhn.fhir.rest.api.MethodOutcome; import java.util.List;
import ca.uhn.fhir.rest.client.MyPatientWithExtensions; import java.util.concurrent.TimeUnit;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class CreateR4Test { 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); 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 int ourPort;
private static Server ourServer; private static Server ourServer;
public static IBaseOperationOutcome ourReturnOo;
@Before @Before
public void before() { public void before() {
ourReturnOo = null; 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 * #472
*/ */
@ -91,28 +112,6 @@ public class CreateR4Test {
assertThat(responseContent, containsString("DIAG")); 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 @Test
public void testCreateWithIncorrectContent1() throws Exception { 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 @Test
public void testSearch() throws Exception { public void testSearch() throws Exception {
@ -231,6 +252,7 @@ public class CreateR4Test {
@Create() @Create()
public MethodOutcome create(@ResourceParam Patient theIdParam) { public MethodOutcome create(@ResourceParam Patient theIdParam) {
assertNull(theIdParam.getIdElement().getIdPart());
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo); 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 can be useful in order to allow interceptors to
change payload contents being saved. change payload contents being saved.
</action> </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>
<release version="3.1.0" date="2017-11-23"> <release version="3.1.0" date="2017-11-23">
<action type="add"> <action type="add">