Server create and update methods failed with an IllegalArgumentException if the method type was a custom resource definition type (instead of a built-in HAPI type). Thanks to Neal Acharya for the analysis.
This commit is contained in:
parent
1063b6cf1e
commit
8f8f9ed308
|
@ -615,7 +615,7 @@ class ModelScanner {
|
|||
private <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
|
||||
T retVal = theTarget.getAnnotation(theAnnotationType);
|
||||
if (retVal == null) {
|
||||
String sourceClassName = theAnnotationType.getClass().getName();
|
||||
String sourceClassName = theAnnotationType.getName();
|
||||
String candidateAltClassName = sourceClassName.replace("ca.uhn.fhir.model.api.annotation", "org.hl7.fhir.instance.model.annotations");
|
||||
|
||||
if (!sourceClassName.equals(candidateAltClassName)) {
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.method;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
|
@ -32,6 +33,7 @@ import java.util.Set;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -68,6 +70,71 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
}
|
||||
}
|
||||
|
||||
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(theRequest.getFhirServerBase());
|
||||
b.append('/');
|
||||
b.append(getResourceName());
|
||||
b.append('/');
|
||||
b.append(response.getId().getIdPart());
|
||||
if (response.getId().hasVersionIdPart()) {
|
||||
b.append("/" + Constants.PARAM_HISTORY + "/");
|
||||
b.append(response.getId().getVersionIdPart());
|
||||
} else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
|
||||
b.append("/" + Constants.PARAM_HISTORY + "/");
|
||||
b.append(response.getVersionId().getValue());
|
||||
}
|
||||
theResponse.addHeader(headerLocation, b.toString());
|
||||
}
|
||||
|
||||
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
|
||||
|
||||
/**
|
||||
* Subclasses may override to allow a void method return type, which is allowable for some methods (e.g. delete)
|
||||
*/
|
||||
protected boolean allowVoidReturnType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource resource);
|
||||
|
||||
/*
|
||||
* @Override public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse)
|
||||
* throws BaseServerResponseException, IOException { Object[] params = new Object[getParameters().size()]; for (int
|
||||
* i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param != null) {
|
||||
* params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null); } }
|
||||
*
|
||||
* addParametersForServerRequest(theRequest, params);
|
||||
*
|
||||
* MethodOutcome response = (MethodOutcome) invokeServerMethod(getProvider(), params);
|
||||
*
|
||||
* if (response == null) { if (myReturnVoid == false) { throw new ConfigurationException("Method " +
|
||||
* getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null"); }
|
||||
* else { theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT); } } else if (!myReturnVoid) { if
|
||||
* (response.isCreated()) { theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED); StringBuilder b = new
|
||||
* StringBuilder(); b.append(theRequest.getFhirServerBase()); b.append('/'); b.append(getResourceName());
|
||||
* b.append('/'); b.append(response.getId().getValue()); if (response.getVersionId() != null &&
|
||||
* response.getVersionId().isEmpty() == false) { b.append("/_history/");
|
||||
* b.append(response.getVersionId().getValue()); } theResponse.addHeader("Location", b.toString()); } else {
|
||||
* theResponse.setStatus(Constants.STATUS_HTTP_200_OK); } } else {
|
||||
* theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT); }
|
||||
*
|
||||
* theServer.addHeadersToResponse(theResponse);
|
||||
*
|
||||
* Writer writer = theResponse.getWriter(); try { if (response != null) { OperationOutcome outcome = new
|
||||
* OperationOutcome(); if (response.getOperationOutcome() != null && response.getOperationOutcome().getIssue() !=
|
||||
* null) { outcome.getIssue().addAll(response.getOperationOutcome().getIssue()); } EncodingUtil encoding =
|
||||
* BaseMethodBinding.determineResponseEncoding(theRequest .getServletRequest(), theRequest.getParameters());
|
||||
* theResponse.setContentType(encoding.getResourceContentType()); IParser parser = encoding.newParser(getContext());
|
||||
* parser.encodeResourceToWriter(outcome, writer); } } finally { writer.close(); } // getMethod().in }
|
||||
*/
|
||||
|
||||
/**
|
||||
* For servers, this method will match only incoming requests that match the given operation, or which have no
|
||||
* operation in the URL if this method returns null.
|
||||
*/
|
||||
protected abstract String getMatchingOperation();
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
|
||||
Set<RequestType> allowableRequestTypes = provideAllowableRequestTypes();
|
||||
|
@ -224,79 +291,20 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
return myReturnVoid;
|
||||
}
|
||||
|
||||
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(theRequest.getFhirServerBase());
|
||||
b.append('/');
|
||||
b.append(getResourceName());
|
||||
b.append('/');
|
||||
b.append(response.getId().getIdPart());
|
||||
if (response.getId().hasVersionIdPart()) {
|
||||
b.append("/" + Constants.PARAM_HISTORY + "/");
|
||||
b.append(response.getId().getVersionIdPart());
|
||||
} else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
|
||||
b.append("/" + Constants.PARAM_HISTORY + "/");
|
||||
b.append(response.getVersionId().getValue());
|
||||
}
|
||||
theResponse.addHeader(headerLocation, b.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse)
|
||||
* throws BaseServerResponseException, IOException { Object[] params = new Object[getParameters().size()]; for (int
|
||||
* i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param != null) {
|
||||
* params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null); } }
|
||||
*
|
||||
* addParametersForServerRequest(theRequest, params);
|
||||
*
|
||||
* MethodOutcome response = (MethodOutcome) invokeServerMethod(getProvider(), params);
|
||||
*
|
||||
* if (response == null) { if (myReturnVoid == false) { throw new ConfigurationException("Method " +
|
||||
* getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null"); }
|
||||
* else { theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT); } } else if (!myReturnVoid) { if
|
||||
* (response.isCreated()) { theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED); StringBuilder b = new
|
||||
* StringBuilder(); b.append(theRequest.getFhirServerBase()); b.append('/'); b.append(getResourceName());
|
||||
* b.append('/'); b.append(response.getId().getValue()); if (response.getVersionId() != null &&
|
||||
* response.getVersionId().isEmpty() == false) { b.append("/_history/");
|
||||
* b.append(response.getVersionId().getValue()); } theResponse.addHeader("Location", b.toString()); } else {
|
||||
* theResponse.setStatus(Constants.STATUS_HTTP_200_OK); } } else {
|
||||
* theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT); }
|
||||
*
|
||||
* theServer.addHeadersToResponse(theResponse);
|
||||
*
|
||||
* Writer writer = theResponse.getWriter(); try { if (response != null) { OperationOutcome outcome = new
|
||||
* OperationOutcome(); if (response.getOperationOutcome() != null && response.getOperationOutcome().getIssue() !=
|
||||
* null) { outcome.getIssue().addAll(response.getOperationOutcome().getIssue()); } EncodingUtil encoding =
|
||||
* BaseMethodBinding.determineResponseEncoding(theRequest .getServletRequest(), theRequest.getParameters());
|
||||
* theResponse.setContentType(encoding.getResourceContentType()); IParser parser = encoding.newParser(getContext());
|
||||
* parser.encodeResourceToWriter(outcome, writer); } } finally { writer.close(); } // getMethod().in }
|
||||
*/
|
||||
|
||||
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
|
||||
|
||||
/**
|
||||
* Subclasses may override to allow a void method return type, which is allowable for some methods (e.g. delete)
|
||||
*/
|
||||
protected boolean allowVoidReturnType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource resource);
|
||||
|
||||
/**
|
||||
* For servers, this method will match only incoming requests that match the given operation, or which have no
|
||||
* operation in the URL if this method returns null.
|
||||
*/
|
||||
protected abstract String getMatchingOperation();
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected IResource parseIncomingServerResource(Request theRequest) throws IOException {
|
||||
EncodingEnum encoding = RestfulServer.determineRequestEncoding(theRequest);
|
||||
IParser parser = encoding.newParser(getContext());
|
||||
IResource resource = parser.parseResource(theRequest.getServletRequest().getReader());
|
||||
return resource;
|
||||
BufferedReader requestReader = theRequest.getServletRequest().getReader();
|
||||
|
||||
Class<? extends IBaseResource> wantedResourceType = requestContainsResourceType();
|
||||
if (wantedResourceType != null) {
|
||||
return (IResource) parser.parseResource(wantedResourceType, requestReader);
|
||||
} else {
|
||||
return parser.parseResource(requestReader);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Set<RequestType> provideAllowableRequestTypes();
|
||||
|
@ -307,6 +315,14 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
protected boolean requestContainsResource() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override to provide a specific resource type that this method wants
|
||||
* as a parameter
|
||||
*/
|
||||
protected Class<? extends IBaseResource> requestContainsResourceType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncoding, HttpServletResponse theResponse, Request theRequest) throws IOException {
|
||||
theResponse.setStatus(theE.getStatusCode());
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
|||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.instance.model.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -42,6 +43,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
private int myResourceParameterIndex;
|
||||
private String myResourceName;
|
||||
private boolean myBinary;
|
||||
private Class<? extends IResource> myResourceType;
|
||||
|
||||
public BaseOutcomeReturningMethodBindingWithResourceParam(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
|
||||
super(theMethod, theContext, theMethodAnnotation, theProvider);
|
||||
|
@ -51,6 +53,10 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
int index = 0;
|
||||
for (IParameter next : getParameters()) {
|
||||
if (next instanceof ResourceParameter) {
|
||||
if (myResourceType != null) {
|
||||
throw new ConfigurationException("Method " + theMethod.getName() + " on type " + theMethod.getDeclaringClass() + " has more than one @ResourceParam. Only one is allowed.");
|
||||
}
|
||||
|
||||
resourceParameter = (ResourceParameter) next;
|
||||
Class<? extends IResource> resourceType = resourceParameter.getResourceType();
|
||||
|
||||
|
@ -62,7 +68,8 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
myBinary = true;
|
||||
}
|
||||
|
||||
myResourceName = theContext.getResourceDefinition(resourceType).getName();
|
||||
myResourceType = resourceParameter.getResourceType();
|
||||
myResourceName = theContext.getResourceDefinition(myResourceType).getName();
|
||||
|
||||
myResourceParameterIndex = index;
|
||||
}
|
||||
|
@ -75,6 +82,11 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends IBaseResource> requestContainsResourceType() {
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IResource parseIncomingServerResource(Request theRequest) throws IOException {
|
||||
if (myBinary) {
|
||||
|
|
|
@ -34,16 +34,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
|
||||
public class ResourceParameter implements IParameter {
|
||||
|
||||
private Class<? extends IResource> myResourceName ;
|
||||
private Class<? extends IResource> myResourceType;
|
||||
|
||||
public ResourceParameter(Class<? extends IResource> theParameterType) {
|
||||
myResourceName =theParameterType;
|
||||
myResourceType = theParameterType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,12 +53,12 @@ public class ResourceParameter implements IParameter {
|
|||
}
|
||||
|
||||
public Class<? extends IResource> getResourceType() {
|
||||
return myResourceName;
|
||||
return myResourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,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.hl7.fhir.instance.model.annotations.ResourceDef;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
@ -25,11 +26,15 @@ import org.junit.Test;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.Extension;
|
||||
import ca.uhn.fhir.model.dstu.resource.AdverseReaction;
|
||||
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
|
||||
import ca.uhn.fhir.model.dstu.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
|
@ -47,8 +52,6 @@ public class CreateTest {
|
|||
private static DiagnosticReportProvider ourReportProvider;
|
||||
private static Server ourServer;
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreate() throws Exception {
|
||||
|
||||
|
@ -70,7 +73,6 @@ public class CreateTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateById() throws Exception {
|
||||
|
||||
|
@ -92,8 +94,6 @@ public class CreateTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateJson() throws Exception {
|
||||
|
||||
|
@ -116,6 +116,27 @@ public class CreateTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCustomType() throws Exception {
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getIdentifier().setValue("001");
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation");
|
||||
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(obs), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
|
||||
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
|
||||
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||
assertEquals("http://localhost:" + ourPort + "/Observation/001/_history/002", status.getFirstHeader("location").getValue());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithUnprocessableEntity() throws Exception {
|
||||
|
||||
|
@ -155,7 +176,7 @@ public class CreateTest {
|
|||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer();
|
||||
servlet.setResourceProviders(patientProvider, ourReportProvider, new DummyAdverseReactionResourceProvider());
|
||||
servlet.setResourceProviders(patientProvider, ourReportProvider, new DummyAdverseReactionResourceProvider(), new CustomObservationProvider());
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
|
@ -180,7 +201,6 @@ public class CreateTest {
|
|||
return DiagnosticReport.class;
|
||||
}
|
||||
|
||||
|
||||
@Create()
|
||||
public MethodOutcome createDiagnosticReport(@ResourceParam DiagnosticReport thePatient) {
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
|
@ -189,7 +209,7 @@ public class CreateTest {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
|
@ -228,10 +248,6 @@ public class CreateTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
|
@ -242,12 +258,48 @@ public class CreateTest {
|
|||
@Create()
|
||||
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
||||
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
|
||||
if (thePatient.getId().isEmpty()==false) {
|
||||
id=thePatient.getId();
|
||||
if (thePatient.getId().isEmpty() == false) {
|
||||
id = thePatient.getId();
|
||||
}
|
||||
return new MethodOutcome(id.withVersion("002"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ResourceDef(name = "Observation")
|
||||
public static class CustomObservation extends Observation {
|
||||
|
||||
@Extension(url = "http://foo#string", definedLocally = false, isModifier = false)
|
||||
@Child(name = "string")
|
||||
private StringDt myString;
|
||||
|
||||
public StringDt getString() {
|
||||
if (myString == null) {
|
||||
myString = new StringDt();
|
||||
}
|
||||
return myString;
|
||||
}
|
||||
|
||||
public void setString(StringDt theString) {
|
||||
myString = theString;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CustomObservationProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IResource> getResourceType() {
|
||||
return Observation.class;
|
||||
}
|
||||
|
||||
@Create()
|
||||
public MethodOutcome createPatient(@ResourceParam CustomObservation thePatient) {
|
||||
IdDt id = new IdDt(thePatient.getIdentifier().getValue().getValue());
|
||||
if (thePatient.getId().isEmpty() == false) {
|
||||
id = thePatient.getId();
|
||||
}
|
||||
return new MethodOutcome(id.withVersion("002"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,11 @@
|
|||
header in server was incorrectly using FHIR date format instead
|
||||
of RFC-1123 format.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server create and update methods failed with an IllegalArgumentException if
|
||||
the method type was a custom resource definition type (instead of a built-in
|
||||
HAPI type). Thanks to Neal Acharya for the analysis.
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.8" date="2014-Dec-17">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue