diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java index 9aec93e9117..75a70ea84d3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3.java @@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.model.api.Bundle; @@ -129,9 +130,20 @@ public class FhirResourceDaoDstu3 extends BaseHapiFhirRe validator.registerValidatorModule(new IdChecker(theMode)); + IBaseResource resourceToValidateById = null; + if (theId != null && theId.hasResourceType() && theId.hasIdPart()) { + Class type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass(); + IFhirResourceDao dao = getDao(type); + resourceToValidateById = dao.read(theId, theRequestDetails); + } + ValidationResult result; if (theResource == null) { - throw new InvalidRequestException("No resource supplied for $validate operation (resource is required unless mode is \"delete\")"); + if (resourceToValidateById != null) { + result = validator.validateWithResult(resourceToValidateById); + } else { + throw new InvalidRequestException("No resource supplied for $validate operation (resource is required unless mode is \"delete\")"); + } } else if (isNotBlank(theRawResource)) { result = validator.validateWithResult(theRawResource); } else { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java index fb896ed52d4..1999b162682 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchNoFtTest.java @@ -610,12 +610,12 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { public void testSearchCompositeParamDate() { Observation o1 = new Observation(); o1.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); - o1.setValue(new Period().setStartElement(new DateTimeType("2001-01-01T11:11:11")).setEndElement(new DateTimeType("2001-01-01T12:11:11"))); + o1.setValue(new Period().setStartElement(new DateTimeType("2001-01-01T11:11:11Z")).setEndElement(new DateTimeType("2001-01-01T12:11:11Z"))); IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless(); Observation o2 = new Observation(); o2.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); - o2.setValue(new Period().setStartElement(new DateTimeType("2001-01-02T11:11:11")).setEndElement(new DateTimeType("2001-01-02T12:11:11"))); + o2.setValue(new Period().setStartElement(new DateTimeType("2001-01-02T11:11:11Z")).setEndElement(new DateTimeType("2001-01-02T12:11:11Z"))); IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless(); { @@ -627,21 +627,21 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test { } { TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); - DateParam v1 = new DateParam(">2001-01-01T10:12:12"); + DateParam v1 = new DateParam(">2001-01-01T10:12:12Z"); CompositeParam val = new CompositeParam(v0, v1); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); } { TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); - DateParam v1 = new DateParam("gt2001-01-01T11:12:12"); + DateParam v1 = new DateParam("gt2001-01-01T11:12:12Z"); CompositeParam val = new CompositeParam(v0, v1); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); } { TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); - DateParam v1 = new DateParam("gt2001-01-01T15:12:12"); + DateParam v1 = new DateParam("gt2001-01-01T15:12:12Z"); CompositeParam val = new CompositeParam(v0, v1); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2)); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu1Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu1Test.java index 377b781c5da..2374b312311 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu1Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu1Test.java @@ -10,6 +10,8 @@ import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; @@ -135,7 +137,7 @@ public class ResourceProviderDstu1Test extends BaseJpaTest { } @Test - public void testCreateWithClientSuppliedId() { + public void testCreateWithClientSuppliedId() throws Exception { deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01"); Patient p1 = new Patient(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index e97f9424fd8..e9acdae24b9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -2706,6 +2706,30 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } } + @Test + public void testValidateResourceInstanceOnServer() throws IOException { + + Patient patient = new Patient(); + patient.addName().addGiven("James"); + patient.setBirthDateElement(new DateType("2011-02-02")); + patient.addContact().setGender(AdministrativeGender.MALE); + patient.addCommunication().setPreferred(true); // missing language + + IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "/$validate"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent()); + ourLog.info(resp); + assertEquals(412, response.getStatusLine().getStatusCode()); + assertThat(resp, containsString("SHALL at least contain a contact's details or a reference to an organization")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + } + // Y @Test public void testValidateResourceHuge() throws IOException { diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java index e5a05d004e7..9f70da54f77 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java @@ -813,6 +813,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private void checkPrimitive(List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) { + if (isBlank(e.primitiveValue())) { + return; + } if (type.equals("boolean")) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'"); } @@ -821,7 +824,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:"); rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace"); } - if (!type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { + if (!type.equalsIgnoreCase("string")) { if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) { warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), "value should not start or finish with whitespace"); } @@ -842,13 +845,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year"); } - if (type.equals("code") && e.primitiveValue() != null) { + if (type.equals("code")) { // Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace // other than single spaces in the contents rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)"); } - if (context.hasBinding() && e.primitiveValue() != null) { + if (context.hasBinding()) { checkPrimitiveBinding(errors, path, type, context, e, profile); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu3Test.java index f9e57d5d00a..e9721044b34 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ValidateDstu3Test.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; @@ -30,6 +31,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -48,6 +50,7 @@ public class ValidateDstu3Test { private static int ourPort; private static Server ourServer; public static Patient ourLastPatient; + private static IdType ourLastId; @Before() public void before() { @@ -58,7 +61,6 @@ public class ValidateDstu3Test { ourLastProfile = null; } - @Test public void testValidate() throws Exception { @@ -218,6 +220,25 @@ public class ValidateDstu3Test { assertThat(resp, stringContainsInOrder(""; + // @formatter:on + + ValidationResult output = myVal.validateWithResult(input); + assertEquals(output.toString(), 1, output.getMessages().size()); + assertThat(output.getMessages().get(0).getMessage(), containsString("Element must have some content")); + } + + @Test + public void testValidateRawXmlResourceWithPrimitiveContainingOnlyAnExtension() { + // @formatter:off + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " </document>\n" + + " <resource>\n" + + " <reference value=\"DocumentReference/123\"/> <!-- DocumentReference -->\n" + + " <display value=\"citation title\"/>\n" + + " </resource>\n" + + " </relatedResource>\n" + + " <category value=\"referral\"/>\n" + + " <code>\n" + + " <coding>\n" + + " <!-- Error: Connection to http://localhost:960 refused -->\n" + + " <!--<system value=\"http://snomed.info/sct\"/>-->\n" + + " <code value=\"306206005\"/>\n" + + " </coding>\n" + + " </code>\n" + + " <!-- Specifying this this way results in a null reference exception in the validator -->\n" + + " <timingTiming>\n" + + " <event>\n" + + " <extension url=\"http://fhir.org/cql-expression\">\n" + + " <valueString value=\"Now()\"/>\n" + + " </extension>\n" + + " </event>\n" + + " </timingTiming>\n" + + " <participantType value=\"practitioner\"/>\n" + + " </ActivityDefinition>"; + // @formatter:on + + ValidationResult output = myVal.validateWithResult(input); + List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output); + assertEquals(output.toString(), 0, res.size()); + } + @Test public void testValidateRawXmlResourceBadAttributes() { //@formatter:off diff --git a/src/changes/changes.xml b/src/changes/changes.xml index d9d9b1afdc8..2c0656b9957 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -404,6 +404,10 @@ end of the resource instead of in the correct order </action> + <action type="fix"> + Fix STU3 JPA resource providers to allow validate operation + at instance level + </action> </release> <release version="1.5" date="2016-04-20"> <action type="fix" issue="339">