Validation fixes

This commit is contained in:
James Agnew 2016-07-05 16:56:06 -04:00
parent 9226e43090
commit d63e289cbe
8 changed files with 138 additions and 17 deletions

View File

@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; 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.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
@ -129,9 +130,20 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
validator.registerValidatorModule(new IdChecker(theMode)); validator.registerValidatorModule(new IdChecker(theMode));
IBaseResource resourceToValidateById = null;
if (theId != null && theId.hasResourceType() && theId.hasIdPart()) {
Class<? extends IBaseResource> type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass();
IFhirResourceDao<? extends IBaseResource> dao = getDao(type);
resourceToValidateById = dao.read(theId, theRequestDetails);
}
ValidationResult result; ValidationResult result;
if (theResource == null) { 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)) { } else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource); result = validator.validateWithResult(theRawResource);
} else { } else {

View File

@ -610,12 +610,12 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
public void testSearchCompositeParamDate() { public void testSearchCompositeParamDate() {
Observation o1 = new Observation(); Observation o1 = new Observation();
o1.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); 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(); IIdType id1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
Observation o2 = new Observation(); Observation o2 = new Observation();
o2.getCode().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); 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(); IIdType id2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless();
{ {
@ -627,21 +627,21 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
} }
{ {
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); 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<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1); CompositeParam<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1);
IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val);
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2));
} }
{ {
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); 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<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1); CompositeParam<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1);
IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val);
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2));
} }
{ {
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); 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<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1); CompositeParam<TokenParam, DateParam> val = new CompositeParam<TokenParam, DateParam>(v0, v1);
IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val); IBundleProvider result = myObservationDao.search(Observation.SP_CODE_VALUE_DATE, val);
assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2)); assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id2));

View File

@ -10,6 +10,8 @@ import static org.junit.Assert.fail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -135,7 +137,7 @@ public class ResourceProviderDstu1Test extends BaseJpaTest {
} }
@Test @Test
public void testCreateWithClientSuppliedId() { public void testCreateWithClientSuppliedId() throws Exception {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01"); deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01");
Patient p1 = new Patient(); Patient p1 = new Patient();

View File

@ -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 // Y
@Test @Test
public void testValidateResourceHuge() throws IOException { public void testValidateResourceHuge() throws IOException {

View File

@ -813,6 +813,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) { private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) {
if (isBlank(e.primitiveValue())) {
return;
}
if (type.equals("boolean")) { 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'"); 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().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"); 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")) { 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"); 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"); 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 // 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 // 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)"); 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); checkPrimitiveBinding(errors, path, type, context, e, profile);
} }

View File

@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
@ -30,6 +31,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; 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.ResourceParam;
import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
@ -48,6 +50,7 @@ public class ValidateDstu3Test {
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
public static Patient ourLastPatient; public static Patient ourLastPatient;
private static IdType ourLastId;
@Before() @Before()
public void before() { public void before() {
@ -58,7 +61,6 @@ public class ValidateDstu3Test {
ourLastProfile = null; ourLastProfile = null;
} }
@Test @Test
public void testValidate() throws Exception { public void testValidate() throws Exception {
@ -218,6 +220,25 @@ public class ValidateDstu3Test {
assertThat(resp, stringContainsInOrder("<OperationOutcome", "FOOBAR")); assertThat(resp, stringContainsInOrder("<OperationOutcome", "FOOBAR"));
} }
@Test
public void testValidateWithGet() throws Exception {
ourOutcomeToReturn = new OperationOutcome();
ourOutcomeToReturn.addIssue().setDiagnostics("FOOBAR");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$validate");
HttpResponse status = ourClient.execute(httpGet);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome", "FOOBAR"));
assertEquals(null, ourLastPatient);
assertEquals("Patient", ourLastId.getResourceType());
assertEquals("123", ourLastId.getIdPart());
}
@AfterClass @AfterClass
public static void afterClassClearContext() throws Exception { public static void afterClassClearContext() throws Exception {
ourServer.stop(); ourServer.stop();
@ -271,15 +292,16 @@ public class ValidateDstu3Test {
} }
@Validate() @Validate()
public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) { public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @IdParam IdType theId, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) {
ourLastPatient = thePatient; ourLastPatient = thePatient;
ourLastId = theId;
IdType id; IdType id;
if (thePatient != null) { if (thePatient != null) {
id = new IdType(thePatient.getIdentifier().get(0).getValue()); id = new IdType(thePatient.getIdentifier().get(0).getValue());
if (thePatient.getIdElement().isEmpty() == false) { if (thePatient.getIdElement().isEmpty() == false) {
id = thePatient.getIdElement(); id = thePatient.getIdElement();
} }
} else { } else {
id = new IdType("1"); id = new IdType("1");
} }

View File

@ -422,6 +422,60 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(output.toString(), 0, output.getMessages().size()); assertEquals(output.toString(), 0, output.getMessages().size());
} }
@Test
public void testValidateRawXmlResourceWithEmptyPrimitive() {
// @formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\"><name><given/></name></Patient>";
// @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 = "<ActivityDefinition xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"referralToMentalHealthCare\"/>\n" +
" <status value=\"draft\"/>\n" +
" <description value=\"refer to primary care mental-health integrated care program for evaluation and treatment of mental health conditions now\"/>\n" +
" <relatedResource>\n" +
" <type value=\"citation\"/>\n" +
" <document>\n" +
" <url value=\"blah blah blah\"/>\n" +
" <title value=\"citation title\"/>\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 @Test
public void testValidateRawXmlResourceBadAttributes() { public void testValidateRawXmlResourceBadAttributes() {
//@formatter:off //@formatter:off

View File

@ -404,6 +404,10 @@
end of the resource instead of in the correct end of the resource instead of in the correct
order order
</action> </action>
<action type="fix">
Fix STU3 JPA resource providers to allow validate operation
at instance level
</action>
</release> </release>
<release version="1.5" date="2016-04-20"> <release version="1.5" date="2016-04-20">
<action type="fix" issue="339"> <action type="fix" issue="339">