diff --git a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java b/examples/src/main/java/example/RestfulPatientResourceProviderMore.java index ce068006e7c..fb44a826950 100644 --- a/examples/src/main/java/example/RestfulPatientResourceProviderMore.java +++ b/examples/src/main/java/example/RestfulPatientResourceProviderMore.java @@ -71,6 +71,7 @@ import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenOrListParam; 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.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -758,6 +759,23 @@ public MethodOutcome updatePatientConditional( } //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 @Update public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index b2239eb88d4..ef885208efc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -48,33 +48,33 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu int index = 0; for (IParameter next : getParameters()) { if (next instanceof ResourceParameter) { + resourceParameter = (ResourceParameter) next; + if (resourceParameter.getMode() != ResourceParameter.Mode.RESOURCE) { + continue; + } 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 providerResourceType = resourceParameter.getResourceType(); - - if (theProvider instanceof IResourceProvider) { - providerResourceType = ((IResourceProvider) theProvider).getResourceType(); - } - myResourceType = resourceParameter.getResourceType(); - if (Modifier.isAbstract(myResourceType.getModifiers())) { - myResourceType = providerResourceType; - } - - myResourceName = theContext.getResourceDefinition(providerResourceType).getName(); myResourceParameterIndex = 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); 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()); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index 474d340a625..c32af007c00 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -73,6 +73,7 @@ import ca.uhn.fhir.rest.param.NumberAndListParam; import ca.uhn.fhir.rest.param.QuantityAndListParam; import ca.uhn.fhir.rest.param.ReferenceAndListParam; 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.TokenAndListParam; import ca.uhn.fhir.rest.param.TransactionParameter; @@ -408,10 +409,17 @@ public class MethodUtil { param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType); } 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()); } - param = new ResourceParameter((Class) parameterType, theProvider); + param = new ResourceParameter((Class) parameterType, theProvider, mode); } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { param = new NullParameter(); } else if (nextAnnotation instanceof ServerBase) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java index 8d582153510..6d49477c35b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.Validate; import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IBaseBinary; 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.MethodUtil; 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.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -59,11 +61,16 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class ResourceParameter implements IParameter { private boolean myBinary; + private Mode myMode; private Class myResourceType; - public ResourceParameter(Class theParameterType, Object theProvider) { + public ResourceParameter(Class theParameterType, Object theProvider, Mode theMode) { + Validate.notNull(theParameterType, "theParameterType can not be null"); + Validate.notNull(theMode, "theMode can not be null"); + myResourceType = theParameterType; myBinary = IBaseBinary.class.isAssignableFrom(theParameterType); + myMode = theMode; Class providerResourceType = null; if (theProvider instanceof IResourceProvider) { @@ -76,6 +83,10 @@ public class ResourceParameter implements IParameter { } + public Mode getMode() { + return myMode; + } + public Class getResourceType() { return myResourceType; } @@ -93,9 +104,21 @@ public class ResourceParameter implements IParameter { @Override 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) { - FhirContext ctx = theRequest.getServer().getFhirContext(); String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); @@ -110,6 +133,29 @@ public class ResourceParameter implements IParameter { 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 theResourceType) { FhirContext ctx = theRequest.getServer().getFhirContext(); @@ -175,27 +221,8 @@ public class ResourceParameter implements IParameter { 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 enum Mode{ + BODY, ENCODING, RESOURCE } } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CreateTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CreateTest.java index 99d15561c4d..f8b9afdcc4a 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CreateTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/CreateTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.server; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.io.StringReader; @@ -20,6 +21,7 @@ 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; @@ -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.Observation; 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.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; @@ -48,11 +51,21 @@ import ca.uhn.fhir.util.PortUtil; */ public class CreateTest { 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 int ourPort; + private static DiagnosticReportProvider ourReportProvider; + private static Server ourServer; + @Before() + public void before() { + ourLastResourceBody=null; + ourLastEncoding=null; + } + @Test public void testCreate() throws Exception { @@ -73,6 +86,34 @@ public class CreateTest { 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(ourLastResourceBody, stringContainsInOrder(""," getResourceType() { + return Observation.class; + } + + } + public static class DiagnosticReportProvider implements IResourceProvider { 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() { return myLastTags; } @@ -203,13 +289,6 @@ public class CreateTest { 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 { + @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 public Class getResourceType() { return Patient.class; } + } + + public static class OrganizationProvider implements IResourceProvider { + @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(); - } - return new MethodOutcome(id.withVersion("002")); + public MethodOutcome create(@ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) { + ourLastResourceBody=theResourceBody; + ourLastEncoding=theEncoding; + + 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 public Class 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")); + return Organization.class; } } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ValidateTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ValidateTest.java new file mode 100644 index 00000000000..30e98522a33 --- /dev/null +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ValidateTest.java @@ -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("", " 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 getResourceType() { + return Organization.class; + } + + } + +} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/InstanceValidator.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/InstanceValidator.java deleted file mode 100644 index 97da2926ca2..00000000000 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/InstanceValidator.java +++ /dev/null @@ -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 validate(String theInput, Class 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 results = v.validate(document, profile); - return results; - } catch (Exception e) { - throw new InternalErrorException("Unexpected failure while validating resource", e); - } - } - -} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/StructureDefinitionValidator.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/StructureDefinitionValidator.java new file mode 100644 index 00000000000..7456a69eb9e --- /dev/null +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/validation/StructureDefinitionValidator.java @@ -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 validate(String theInput, EncodingEnum theEncoding, Class 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 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); + } + + } + +} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/InstanceValidatorTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/InstanceValidatorTest.java deleted file mode 100644 index e88eb3616e5..00000000000 --- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/InstanceValidatorTest.java +++ /dev/null @@ -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 = "" - + "" - + ""; - - InstanceValidator val = new InstanceValidator(ourCtx); - List output = val.validate(input, Patient.class); - assertEquals(output.toString(), 0, output.size()); - } - - @Test - public void testValidateXmlResourceBadAttributes() { - String input = "" - + "" - + "" - + ""; - - InstanceValidator val = new InstanceValidator(ourCtx); - List output = val.validate(input, Patient.class); - assertEquals(output.toString(), 1, output.size()); - assertThat(output.toString(), containsString("/f:Patient/f:foo Element is unknown")); - } - -} diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/StructureDefinitionValidatorTest.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/StructureDefinitionValidatorTest.java new file mode 100644 index 00000000000..59b432f5152 --- /dev/null +++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/validation/StructureDefinitionValidatorTest.java @@ -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 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 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 = "" + + "" + + ""; + + StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx); + List output = val.validate(input, EncodingEnum.XML, Patient.class); + assertEquals(output.toString(), 0, output.size()); + } + + + @Test + public void testValidateXmlResourceBadAttributes() { + String input = "" + + "" + + "" + + ""; + + StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx); + List 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")); + } +} diff --git a/src/site/xdoc/doc_rest_operations.xml b/src/site/xdoc/doc_rest_operations.xml index 83c25d4188c..c65e9b1e070 100644 --- a/src/site/xdoc/doc_rest_operations.xml +++ b/src/site/xdoc/doc_rest_operations.xml @@ -177,6 +177,24 @@ http://fhir.example.com/Patient?identifier=system%7C00001

+ +

Accessing The Raw Resource Payload

+

+ 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 @ResourceParam of type + String (to access the raw resource body) and/or + EncodingEnum (to determine which encoding was used) +

+

+ The following example shows how to use these additonal data elements. +

+ + + + + +
@@ -339,6 +357,13 @@ http://fhir.example.com/Patient
If-None-Exist: Patient?identifier=system%7C0001

+

Accessing The Raw Resource Payload

+

+ The create operation also supports access to the raw payload, + using the same semantics as raw payload access + for the update operation. +

+