Allow raw access to resource body in create/update/etc

This commit is contained in:
jamesagnew 2015-06-01 09:21:41 -04:00
parent 064f113133
commit e5b402cb14
11 changed files with 567 additions and 226 deletions

View File

@ -71,6 +71,7 @@ import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
@ -758,6 +759,23 @@ public MethodOutcome updatePatientConditional(
} }
//END SNIPPET: updateConditional //END SNIPPET: updateConditional
//START SNIPPET: updateRaw
@Update
public MethodOutcome updatePatientWithRawValue (
@ResourceParam Patient thePatient,
@IdParam IdDt theId,
@ResourceParam String theRawBody,
@ResourceParam EncodingEnum theEncodingEnum) {
// Here, thePatient will have the parsed patient body, but
// theRawBody will also have the raw text of the resource
// being created, and theEncodingEnum will tell you which
// encoding was used
return new MethodOutcome(); // populate this
}
//END SNIPPET: updateRaw
//START SNIPPET: update //START SNIPPET: update
@Update @Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) { public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {

View File

@ -48,33 +48,33 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
int index = 0; int index = 0;
for (IParameter next : getParameters()) { for (IParameter next : getParameters()) {
if (next instanceof ResourceParameter) { if (next instanceof ResourceParameter) {
resourceParameter = (ResourceParameter) next;
if (resourceParameter.getMode() != ResourceParameter.Mode.RESOURCE) {
continue;
}
if (myResourceType != null) { if (myResourceType != null) {
throw new ConfigurationException("Method " + theMethod.getName() + " on type " + theMethod.getDeclaringClass() + " has more than one @ResourceParam. Only one is allowed."); throw new ConfigurationException("Method " + theMethod.getName() + " on type " + theMethod.getDeclaringClass() + " has more than one @ResourceParam. Only one is allowed.");
} }
resourceParameter = (ResourceParameter) next;
Class<? extends IBaseResource> providerResourceType = resourceParameter.getResourceType();
if (theProvider instanceof IResourceProvider) {
providerResourceType = ((IResourceProvider) theProvider).getResourceType();
}
myResourceType = resourceParameter.getResourceType(); myResourceType = resourceParameter.getResourceType();
if (Modifier.isAbstract(myResourceType.getModifiers())) {
myResourceType = providerResourceType;
}
myResourceName = theContext.getResourceDefinition(providerResourceType).getName();
myResourceParameterIndex = index; myResourceParameterIndex = index;
} }
index++; index++;
} }
if ((myResourceType == null || Modifier.isAbstract(myResourceType.getModifiers())) && (theProvider instanceof IResourceProvider)) {
myResourceType = ((IResourceProvider) theProvider).getResourceType();
}
if (myResourceType == null) {
throw new ConfigurationException("Unable to determine resource type for method: " + theMethod);
}
myResourceName = theContext.getResourceDefinition(myResourceType).getName();
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod); myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
if (resourceParameter == null) { if (resourceParameter == null) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with @" + ResourceParam.class.getSimpleName()); throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a resource parameter annotated with @" + ResourceParam.class.getSimpleName());
} }
} }

View File

@ -73,6 +73,7 @@ import ca.uhn.fhir.rest.param.NumberAndListParam;
import ca.uhn.fhir.rest.param.QuantityAndListParam; import ca.uhn.fhir.rest.param.QuantityAndListParam;
import ca.uhn.fhir.rest.param.ReferenceAndListParam; import ca.uhn.fhir.rest.param.ReferenceAndListParam;
import ca.uhn.fhir.rest.param.ResourceParameter; import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TransactionParameter; import ca.uhn.fhir.rest.param.TransactionParameter;
@ -408,10 +409,17 @@ public class MethodUtil {
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType); param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType);
} else if (nextAnnotation instanceof ResourceParam) { } else if (nextAnnotation instanceof ResourceParam) {
if (!IResource.class.isAssignableFrom(parameterType)) { Mode mode;
if (IBaseResource.class.isAssignableFrom(parameterType)) {
mode = Mode.RESOURCE;
} else if (String.class.equals(parameterType)) {
mode = ResourceParameter.Mode.BODY;
} else if (EncodingEnum.class.equals(parameterType)) {
mode = Mode.ENCODING;
} else {
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName()); throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
} }
param = new ResourceParameter((Class<? extends IResource>) parameterType, theProvider); param = new ResourceParameter((Class<? extends IResource>) parameterType, theProvider, mode);
} else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
param = new NullParameter(); param = new NullParameter();
} else if (nextAnnotation instanceof ServerBase) { } else if (nextAnnotation instanceof ServerBase) {

View File

@ -35,6 +35,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -49,6 +50,7 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
@ -59,11 +61,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ResourceParameter implements IParameter { public class ResourceParameter implements IParameter {
private boolean myBinary; private boolean myBinary;
private Mode myMode;
private Class<? extends IBaseResource> myResourceType; private Class<? extends IBaseResource> myResourceType;
public ResourceParameter(Class<? extends IResource> theParameterType, Object theProvider) { public ResourceParameter(Class<? extends IResource> theParameterType, Object theProvider, Mode theMode) {
Validate.notNull(theParameterType, "theParameterType can not be null");
Validate.notNull(theMode, "theMode can not be null");
myResourceType = theParameterType; myResourceType = theParameterType;
myBinary = IBaseBinary.class.isAssignableFrom(theParameterType); myBinary = IBaseBinary.class.isAssignableFrom(theParameterType);
myMode = theMode;
Class<? extends IBaseResource> providerResourceType = null; Class<? extends IBaseResource> providerResourceType = null;
if (theProvider instanceof IResourceProvider) { if (theProvider instanceof IResourceProvider) {
@ -76,6 +83,10 @@ public class ResourceParameter implements IParameter {
} }
public Mode getMode() {
return myMode;
}
public Class<? extends IBaseResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return myResourceType; return myResourceType;
} }
@ -93,9 +104,21 @@ public class ResourceParameter implements IParameter {
@Override @Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException { public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
switch (myMode) {
case BODY:
try {
return IOUtils.toString(createRequestReader(theRequest, theRequestContents));
} catch (IOException e) {
// Shouldn't happen since we're reading from a byte array
throw new InternalErrorException("Failed to load request");
}
case ENCODING:
return RestfulServerUtils.determineRequestEncoding(theRequest);
case RESOURCE:
break;
}
if (myBinary) { if (myBinary) {
FhirContext ctx = theRequest.getServer().getFhirContext(); FhirContext ctx = theRequest.getServer().getFhirContext();
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance();
@ -110,6 +133,29 @@ public class ResourceParameter implements IParameter {
return retVal; return retVal;
} }
static Reader createRequestReader(byte[] theRequestContents, Charset charset) {
Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset);
return requestReader;
}
static Reader createRequestReader(Request theRequest, byte[] theRequestContents) {
return createRequestReader(theRequestContents, determineRequestCharset(theRequest));
}
static Charset determineRequestCharset(Request theRequest) {
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
Charset charset = null;
if (isNotBlank(ct)) {
ContentType parsedCt = ContentType.parse(ct);
charset = parsedCt.getCharset();
}
if (charset == null) {
charset = Charset.forName("UTF-8");
}
return charset;
}
public static IBaseResource loadResourceFromRequest(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) { public static IBaseResource loadResourceFromRequest(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
FhirContext ctx = theRequest.getServer().getFhirContext(); FhirContext ctx = theRequest.getServer().getFhirContext();
@ -175,27 +221,8 @@ public class ResourceParameter implements IParameter {
return retVal; return retVal;
} }
static Reader createRequestReader(byte[] theRequestContents, Charset charset) { public enum Mode{
Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset); BODY, ENCODING, RESOURCE
return requestReader;
}
static Reader createRequestReader(Request theRequest, byte[] theRequestContents) {
return createRequestReader(theRequestContents, determineRequestCharset(theRequest));
}
static Charset determineRequestCharset(Request theRequest) {
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
Charset charset = null;
if (isNotBlank(ct)) {
ContentType parsedCt = ContentType.parse(ct);
charset = parsedCt.getCharset();
}
if (charset == null) {
charset = Charset.forName("UTF-8");
}
return charset;
} }
} }

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.StringReader; import java.io.StringReader;
@ -20,6 +21,7 @@ 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.hamcrest.core.StringContains;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -33,6 +35,7 @@ import ca.uhn.fhir.model.dstu.resource.AdverseReaction;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
@ -48,11 +51,21 @@ import ca.uhn.fhir.util.PortUtil;
*/ */
public class CreateTest { public class CreateTest {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static EncodingEnum ourLastEncoding;
private static String ourLastResourceBody;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateTest.class);
private static int ourPort; private static int ourPort;
private static DiagnosticReportProvider ourReportProvider; private static DiagnosticReportProvider ourReportProvider;
private static Server ourServer; private static Server ourServer;
@Before()
public void before() {
ourLastResourceBody=null;
ourLastEncoding=null;
}
@Test @Test
public void testCreate() throws Exception { public void testCreate() throws Exception {
@ -73,6 +86,34 @@ public class CreateTest {
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue()); assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), StringContains.containsString("UTF-8")); assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), StringContains.containsString("UTF-8"));
assertThat(ourLastResourceBody, stringContainsInOrder("<Patient ", "<identifier>","<value value=\"001"));
assertEquals(EncodingEnum.XML, ourLastEncoding);
}
@Test
public void testCreateWithNoParsed() throws Exception {
Organization org = new Organization();
org.addIdentifier().setValue("001");
org.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Organization");
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(org), 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 + "/Organization/001", status.getFirstHeader("location").getValue());
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), StringContains.containsString("UTF-8"));
assertThat(ourLastResourceBody, stringContainsInOrder("\"resourceType\":\"Organization\"", "\"identifier\"","\"value\":\"001"));
assertEquals(EncodingEnum.JSON, ourLastEncoding);
} }
@Test @Test
@ -96,6 +137,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 @Test
public void testCreateJson() throws Exception { public void testCreateJson() throws Exception {
@ -118,27 +180,6 @@ 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 @Test
public void testCreateWithUnprocessableEntity() throws Exception { public void testCreateWithUnprocessableEntity() throws Exception {
@ -178,7 +219,9 @@ public class CreateTest {
ServletHandler proxyHandler = new ServletHandler(); ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(); RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider, ourReportProvider, new DummyAdverseReactionResourceProvider(), new CustomObservationProvider()); servlet.setResourceProviders(
patientProvider, ourReportProvider, new DummyAdverseReactionResourceProvider(), new CustomObservationProvider(),
new OrganizationProvider());
ServletHolder servletHolder = new ServletHolder(servlet); ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
@ -189,11 +232,54 @@ public class CreateTest {
builder.setConnectionManager(connectionManager); builder.setConnectionManager(connectionManager);
ourClient = builder.build(); ourClient = builder.build();
}
@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 {
@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"));
}
@Override
public Class<? extends IResource> getResourceType() {
return Observation.class;
}
} }
public static class DiagnosticReportProvider implements IResourceProvider { public static class DiagnosticReportProvider implements IResourceProvider {
private TagList myLastTags; private TagList myLastTags;
@Create()
public MethodOutcome createDiagnosticReport(@ResourceParam DiagnosticReport thePatient) {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue().setDetails("FOOBAR");
throw new UnprocessableEntityException(outcome);
}
public TagList getLastTags() { public TagList getLastTags() {
return myLastTags; return myLastTags;
} }
@ -203,13 +289,6 @@ public class CreateTest {
return DiagnosticReport.class; return DiagnosticReport.class;
} }
@Create()
public MethodOutcome createDiagnosticReport(@ResourceParam DiagnosticReport thePatient) {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue().setDetails("FOOBAR");
throw new UnprocessableEntityException(outcome);
}
} }
/** /**
@ -252,55 +331,39 @@ public class CreateTest {
public static class PatientProvider implements IResourceProvider { public static class PatientProvider implements IResourceProvider {
@Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient, @ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
if (thePatient.getId().isEmpty() == false) {
id = thePatient.getId();
}
ourLastResourceBody=theResourceBody;
ourLastEncoding=theEncoding;
return new MethodOutcome(id.withVersion("002"));
}
@Override @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Patient.class; return Patient.class;
} }
}
public static class OrganizationProvider implements IResourceProvider {
@Create() @Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient) { public MethodOutcome create(@ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue()); ourLastResourceBody=theResourceBody;
if (thePatient.getId().isEmpty() == false) { ourLastEncoding=theEncoding;
id = thePatient.getId();
}
return new MethodOutcome(id.withVersion("002"));
}
return new MethodOutcome(new IdDt("001"));
} }
@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 @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Observation.class; return Organization.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"));
} }
} }

View File

@ -0,0 +1,155 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
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.hamcrest.core.StringContains;
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.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
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.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class ValidateTest {
private static CloseableHttpClient ourClient;
private static EncodingEnum ourLastEncoding;
private static String ourLastResourceBody;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class);
private static int ourPort;
private static Server ourServer;
@Before()
public void before() {
ourLastResourceBody = null;
ourLastEncoding = null;
}
@Test
public void testValidate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/_validate");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(204, status.getStatusLine().getStatusCode());
assertThat(ourLastResourceBody, stringContainsInOrder("<Patient ", "<identifier>", "<value value=\"001"));
assertEquals(EncodingEnum.XML, ourLastEncoding);
}
@Test
public void testValidateWithNoParsed() throws Exception {
Organization org = new Organization();
org.addIdentifier().setValue("001");
org.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Organization/_validate");
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(org), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(204, status.getStatusLine().getStatusCode());
assertThat(ourLastResourceBody, stringContainsInOrder("\"resourceType\":\"Organization\"", "\"identifier\"", "\"value\":\"001"));
assertEquals(EncodingEnum.JSON, ourLastEncoding);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider, new OrganizationProvider());
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 PatientProvider implements IResourceProvider {
@Validate()
public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
if (thePatient.getId().isEmpty() == false) {
id = thePatient.getId();
}
ourLastResourceBody = theResourceBody;
ourLastEncoding = theEncoding;
return new MethodOutcome(id.withVersion("002"));
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
public static class OrganizationProvider implements IResourceProvider {
@Validate()
public MethodOutcome validate(@ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
ourLastResourceBody = theResourceBody;
ourLastEncoding = theEncoding;
return new MethodOutcome(new IdDt("001"));
}
@Override
public Class<? extends IResource> getResourceType() {
return Organization.class;
}
}
}

View File

@ -1,77 +0,0 @@
package ca.uhn.fhir.validation;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.utils.WorkerContext;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class InstanceValidator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InstanceValidator.class);
private FhirContext myCtx;
private DocumentBuilderFactory myDocBuilderFactory;
InstanceValidator(FhirContext theContext) {
myCtx = theContext;
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
}
public List<ValidationMessage> validate(String theInput, Class<? extends IBaseResource> theResourceType) {
WorkerContext workerContext = new WorkerContext();
org.hl7.fhir.instance.validation.InstanceValidator v;
try {
v = new org.hl7.fhir.instance.validation.InstanceValidator(workerContext);
} catch (Exception e) {
throw new ConfigurationException(e);
}
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
String profileCpName = "/org/hl7/fhir/instance/model/profile/" + myCtx.getResourceDefinition(theResourceType).getName().toLowerCase() + ".profile.xml";
String profileText;
try {
profileText = IOUtils.toString(InstanceValidator.class.getResourceAsStream(profileCpName), "UTF-8");
} catch (IOException e1) {
throw new ConfigurationException("Failed to load profile from classpath: " + profileCpName, e1);
}
StructureDefinition profile = myCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
try {
List<ValidationMessage> results = v.validate(document, profile);
return results;
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}

View File

@ -0,0 +1,94 @@
package ca.uhn.fhir.validation;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collections;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.utils.WorkerContext;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class StructureDefinitionValidator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StructureDefinitionValidator.class);
private FhirContext myCtx;
private DocumentBuilderFactory myDocBuilderFactory;
StructureDefinitionValidator(FhirContext theContext) {
myCtx = theContext;
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
}
public List<ValidationMessage> validate(String theInput, EncodingEnum theEncoding, Class<? extends IBaseResource> theResourceType) {
WorkerContext workerContext = new WorkerContext();
org.hl7.fhir.instance.validation.InstanceValidator v;
try {
v = new org.hl7.fhir.instance.validation.InstanceValidator(workerContext);
} catch (Exception e) {
throw new ConfigurationException(e);
}
String profileCpName = "/org/hl7/fhir/instance/model/profile/" + myCtx.getResourceDefinition(theResourceType).getName().toLowerCase() + ".profile.xml";
String profileText;
try {
profileText = IOUtils.toString(StructureDefinitionValidator.class.getResourceAsStream(profileCpName), "UTF-8");
} catch (IOException e1) {
throw new ConfigurationException("Failed to load profile from classpath: " + profileCpName, e1);
}
StructureDefinition profile = myCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
try {
List<ValidationMessage> results = v.validate(document, profile);
return results;
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
try {
return v.validate(json, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
}
}

View File

@ -1,42 +0,0 @@
package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.List;
import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
public class InstanceValidatorTest {
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
@Test
public void testValidateXmlResource() {
String input = "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"123\"/>"
+ "</Patient>";
InstanceValidator val = new InstanceValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, Patient.class);
assertEquals(output.toString(), 0, output.size());
}
@Test
public void testValidateXmlResourceBadAttributes() {
String input = "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"123\"/>"
+ "<foo value=\"222\"/>"
+ "</Patient>";
InstanceValidator val = new InstanceValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, Patient.class);
assertEquals(output.toString(), 1, output.size());
assertThat(output.toString(), containsString("/f:Patient/f:foo Element is unknown"));
}
}

View File

@ -0,0 +1,70 @@
package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.List;
import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class StructureDefinitionValidatorTest {
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
@Test
public void testValidateJsonResource() {
String input = "{"
+ "\"resourceType\":\"Patient\","
+ "\"id\":\"123\""
+ "}";
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, EncodingEnum.JSON, Patient.class);
assertEquals(output.toString(), 0, output.size());
}
@Test
public void testValidateJsonResourceBadAttributes() {
String input = "{"
+ "\"resourceType\":\"Patient\","
+ "\"id\":\"123\","
+ "\"foo\":\"123\""
+ "}";
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, EncodingEnum.JSON, Patient.class);
assertEquals(output.toString(), 1, output.size());
assertThat(output.toString(), containsString("/foo Element is unknown"));
}
@Test
public void testValidateXmlResource() {
String input = "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"123\"/>"
+ "</Patient>";
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, EncodingEnum.XML, Patient.class);
assertEquals(output.toString(), 0, output.size());
}
@Test
public void testValidateXmlResourceBadAttributes() {
String input = "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<id value=\"123\"/>"
+ "<foo value=\"222\"/>"
+ "</Patient>";
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
List<ValidationMessage> output = val.validate(input, EncodingEnum.XML, Patient.class);
assertEquals(output.toString(), 1, output.size());
assertThat(output.toString(), containsString("/f:Patient/f:foo Element is unknown"));
}
}

View File

@ -177,6 +177,24 @@
<code>http://fhir.example.com/Patient?identifier=system%7C00001</code> <code>http://fhir.example.com/Patient?identifier=system%7C00001</code>
</p> </p>
<a name="raw_update_access"/>
<h4>Accessing The Raw Resource Payload</h4>
<p>
If you wish to have access to the raw resource payload as well as the parsed value
for any reason, you may also add parameters which have been annotated
with the <code>@ResourceParam</code> of type
<code>String</code> (to access the raw resource body) and/or
<code>EncodingEnum</code> (to determine which encoding was used)
</p>
<p>
The following example shows how to use these additonal data elements.
</p>
<macro name="snippet">
<param name="id" value="updateRaw" />
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<a name="instance_delete" /> <a name="instance_delete" />
</section> </section>
@ -339,6 +357,13 @@
<code>http://fhir.example.com/Patient<br/>If-None-Exist: Patient?identifier=system%7C0001</code> <code>http://fhir.example.com/Patient<br/>If-None-Exist: Patient?identifier=system%7C0001</code>
</p> </p>
<h4>Accessing The Raw Resource Payload</h4>
<p>
The create operation also supports access to the raw payload,
using the same semantics as raw payload access
<a href="#raw_update_access">for the update operation</a>.
</p>
<a name="type_search" /> <a name="type_search" />
</section> </section>