Fix #297 - Resource not populated in ActionRequestDetails for validate method
This commit is contained in:
parent
6cc60624c6
commit
43bdfc0345
|
@ -243,6 +243,7 @@ public class FhirContext {
|
|||
* for extending the core library.
|
||||
*/
|
||||
public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) {
|
||||
Validate.notNull(theResource, "theResource must not be null");
|
||||
return getResourceDefinition(theResource.getClass());
|
||||
}
|
||||
|
||||
|
|
|
@ -303,8 +303,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
|||
* The method params as generated by the specific method binding
|
||||
*/
|
||||
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
|
||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.method;
|
|||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -50,11 +51,13 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
|
|||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||
import ca.uhn.fhir.rest.param.ResourceParameter;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
@ -62,6 +65,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationMethodBinding.class);
|
||||
private boolean myCanOperateAtInstanceLevel;
|
||||
private boolean myCanOperateAtServerLevel;
|
||||
private boolean myCanOperateAtTypeLevel;
|
||||
private String myDescription;
|
||||
private final boolean myIdempotent;
|
||||
private final Integer myIdParamIndex;
|
||||
|
@ -69,7 +73,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
private final RestOperationTypeEnum myOtherOperatiopnType;
|
||||
private List<ReturnType> myReturnParams;
|
||||
private final ReturnTypeEnum myReturnType;
|
||||
private boolean myCanOperateAtTypeLevel;
|
||||
|
||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
||||
OperationParam[] theReturnParams) {
|
||||
|
@ -86,7 +89,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
} else {
|
||||
myCanOperateAtTypeLevel = true;
|
||||
}
|
||||
|
||||
|
||||
Description description = theMethod.getAnnotation(Description.class);
|
||||
if (description != null) {
|
||||
myDescription = description.formalDefinition();
|
||||
|
@ -179,16 +182,16 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myOtherOperatiopnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BundleTypeEnum getResponseBundleType() {
|
||||
return BundleTypeEnum.COLLECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myOtherOperatiopnType;
|
||||
}
|
||||
|
||||
public List<ReturnType> getReturnParams() {
|
||||
return Collections.unmodifiableList(myReturnParams);
|
||||
}
|
||||
|
@ -211,7 +214,13 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
if (!myName.equals(theRequest.getOperation())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
RequestTypeEnum requestType = theRequest.getRequestType();
|
||||
if (requestType != RequestTypeEnum.GET && requestType != RequestTypeEnum.POST) {
|
||||
// Operations can only be invoked with GET and POST
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean requestHasId = theRequest.getId() != null;
|
||||
if (requestHasId) {
|
||||
if (isCanOperateAtInstanceLevel() == false) {
|
||||
|
@ -246,9 +255,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
|
||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException {
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
|
||||
// always ok
|
||||
// all good
|
||||
} else if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
||||
if (!myIdempotent) {
|
||||
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.POST.name());
|
||||
|
@ -273,6 +282,15 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
|
||||
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, this, null);
|
||||
theRequest.getUserData().put(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY, requestContents);
|
||||
}
|
||||
return super.invokeServer(theServer, theRequest);
|
||||
}
|
||||
|
||||
public boolean isCanOperateAtInstanceLevel() {
|
||||
return this.myCanOperateAtInstanceLevel;
|
||||
}
|
||||
|
@ -285,6 +303,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return myIdempotent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateActionRequestDetailsForInterceptor(RequestDetails theRequestDetails, ActionRequestDetails theDetails, Object[] theMethodParams) {
|
||||
super.populateActionRequestDetailsForInterceptor(theRequestDetails, theDetails, theMethodParams);
|
||||
theDetails.setResource((IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY));
|
||||
}
|
||||
|
||||
public void setDescription(String theDescription) {
|
||||
myDescription = theDescription;
|
||||
}
|
||||
|
|
|
@ -50,7 +50,6 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|||
import ca.uhn.fhir.rest.api.ValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.param.CollectionBinder;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ResourceParameter;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
|
@ -59,6 +58,8 @@ import ca.uhn.fhir.util.ParametersUtil;
|
|||
|
||||
public class OperationParameter implements IParameter {
|
||||
|
||||
static final String REQUEST_CONTENTS_USERDATA_KEY = OperationParam.class.getName() + "_PARSED_RESOURCE";
|
||||
|
||||
private IConverter myConverter;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Class<? extends Collection> myInnerCollectionType;
|
||||
|
@ -210,14 +211,7 @@ public class OperationParameter implements IParameter {
|
|||
|
||||
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||
|
||||
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Class<? extends IBaseResource> wantedResourceType = theMethodBinding.getContext().getResourceDefinition("Parameters").getImplementingClass();
|
||||
Class<IBaseResource> wantedResourceType = null;
|
||||
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, theMethodBinding, wantedResourceType);
|
||||
|
||||
IBaseResource requestContents = (IBaseResource) theRequest.getUserData().get(REQUEST_CONTENTS_USERDATA_KEY);
|
||||
RuntimeResourceDefinition def = ctx.getResourceDefinition(requestContents);
|
||||
if (def.getName().equals("Parameters")) {
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
|||
import ca.uhn.fhir.rest.param.ResourceParameter;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
|
||||
public class ValidateMethodBindingDstu2 extends OperationMethodBinding {
|
||||
|
|
|
@ -621,10 +621,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
}
|
||||
|
||||
/*
|
||||
* Actualy invoke the server method. This call is to a HAPI method binding, which is an object that wraps a specific implementing (user-supplied) method, but handles its input and provides
|
||||
* its output back to the client.
|
||||
* Actualy invoke the server method. This call is to a HAPI method binding, which
|
||||
* is an object that wraps a specific implementing (user-supplied) method, but
|
||||
* handles its input and provides its output back to the client.
|
||||
*
|
||||
* This is basically the end of processing for a successful request, since the method binding replies to the client and closes the response.
|
||||
* This is basically the end of processing for a successful request, since the
|
||||
* method binding replies to the client and closes the response.
|
||||
*/
|
||||
resourceMethod.invokeServer(this, requestDetails);
|
||||
|
||||
|
|
|
@ -45,9 +45,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
public class InterceptorTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
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.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
public class InterceptorDstu3Test {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static RestfulServer servlet;
|
||||
private IServerInterceptor myInterceptor1;
|
||||
private IServerInterceptor myInterceptor2;
|
||||
private static final FhirContext ourCtx = FhirContext.forDstu3();
|
||||
|
||||
@Test
|
||||
public void testValidate() throws Exception {
|
||||
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);
|
||||
when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||
when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||
|
||||
//@formatter:off
|
||||
String input =
|
||||
"{\n" +
|
||||
" \"resourceType\":\"Observation\",\n" +
|
||||
" \"id\":\"1855669\",\n" +
|
||||
" \"meta\":{\n" +
|
||||
" \"versionId\":\"1\",\n" +
|
||||
" \"lastUpdated\":\"2016-02-18T07:41:35.953-05:00\"\n" +
|
||||
" },\n" +
|
||||
" \"status\":\"final\",\n" +
|
||||
" \"subject\":{\n" +
|
||||
" \"reference\":\"Patient/1\"\n" +
|
||||
" },\n" +
|
||||
" \"effectiveDateTime\":\"2016-02-18T07:45:36-05:00\",\n" +
|
||||
" \"valueQuantity\":{\n" +
|
||||
" \"value\":57,\n" +
|
||||
" \"system\":\"http://unitsofmeasure.org\",\n" +
|
||||
" \"code\":\"{Beats}/min\"\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
//@formatter:on
|
||||
|
||||
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, myInterceptor2);
|
||||
order.verify(myInterceptor1, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
order.verify(myInterceptor2, times(1)).incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
order.verify(myInterceptor1, times(1)).incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
order.verify(myInterceptor2, 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);
|
||||
order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture());
|
||||
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
|
||||
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
||||
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
||||
verifyNoMoreInteractions(myInterceptor1);
|
||||
verifyNoMoreInteractions(myInterceptor2);
|
||||
|
||||
assertEquals(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, opTypeCapt.getValue());
|
||||
assertNotNull(arTypeCapt.getValue().getResource());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
myInterceptor1 = mock(IServerInterceptor.class);
|
||||
myInterceptor2 = mock(IServerInterceptor.class);
|
||||
servlet.setInterceptors(myInterceptor1, myInterceptor2);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
servlet = new RestfulServer(ourCtx);
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Validate()
|
||||
public MethodOutcome validate(@ResourceParam Patient theResource) {
|
||||
return new MethodOutcome();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -71,6 +71,12 @@
|
|||
interceptor wants to pass the logged in user's details to other parts
|
||||
of the server.
|
||||
</action>
|
||||
<action type="fix" issue="297">
|
||||
<![CDATA[When <code>IServerInterceptor#incomingRequestPreHandled()</code> is called
|
||||
for a <code>@Validate</code> method, the resource was not populated in the
|
||||
<code>ActionRequestDetails</code> argument. Thanks to Ravi Kuchi for reporting!
|
||||
]]>
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.4" date="2016-02-04">
|
||||
<action type="add">
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="cookie" />
|
||||
<param name="id" value="gzip" />
|
||||
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
||||
</macro>
|
||||
|
||||
|
|
Loading…
Reference in New Issue