Make sure the validator can get at codes defined in notpresent

codesystems
This commit is contained in:
James Agnew 2019-09-26 14:44:09 -04:00
parent 94f87e7282
commit cc0df9850c
9 changed files with 71 additions and 42 deletions

View File

@ -108,9 +108,13 @@ public class OperationOutcomeUtil {
if (theOutcome == null) { if (theOutcome == null) {
return false; return false;
} }
return getIssueCount(theCtx, theOutcome) > 0;
}
public static int getIssueCount(FhirContext theCtx, IBaseOperationOutcome theOutcome) {
RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome);
BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue");
return issueChild.getAccessor().getValues(theOutcome).size() > 0; return issueChild.getAccessor().getValues(theOutcome).size();
} }
public static IBaseOperationOutcome newInstance(FhirContext theCtx) { public static IBaseOperationOutcome newInstance(FhirContext theCtx) {
@ -152,5 +156,4 @@ public class OperationOutcomeUtil {
locationChild.getMutator().addValue(theIssue, locationElem); locationChild.getMutator().addValue(theIssue, locationElem);
} }
} }
} }

View File

@ -173,4 +173,11 @@ public class TermCodeSystemVersion implements Serializable {
"Version ID exceeds maximum length (" + MAX_VERSION_LENGTH + "): " + length(theCodeSystemDisplayName)); "Version ID exceeds maximum length (" + MAX_VERSION_LENGTH + "): " + length(theCodeSystemDisplayName));
myCodeSystemDisplayName = theCodeSystemDisplayName; myCodeSystemDisplayName = theCodeSystemDisplayName;
} }
public TermConcept addConcept() {
TermConcept concept = new TermConcept();
concept.setCodeSystemVersion(this);
getConcepts().add(concept);
return concept;
}
} }

View File

@ -5,10 +5,6 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
@ -174,13 +170,6 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements
@CoverageIgnore @CoverageIgnore
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
SearchParameterMap map = new SearchParameterMap();
map.add(CodeSystem.SP_URL, new UriParam(theSystem));
map.setLoadSynchronousUpTo(1);
IBundleProvider outcome = myCodeSystemResourceDao.search(map);
if (outcome.size() > 0) {
return (CodeSystem) outcome.getResources(0, 1).get(0);
}
return null; return null;
} }

View File

@ -5,10 +5,6 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@ -185,13 +181,6 @@ public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements
@CoverageIgnore @CoverageIgnore
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
SearchParameterMap map = new SearchParameterMap();
map.add(org.hl7.fhir.r5.model.CodeSystem.SP_URL, new UriParam(theSystem));
map.setLoadSynchronousUpTo(1);
IBundleProvider outcome = myCodeSystemResourceDao.search(map);
if (outcome.size() > 0) {
return (org.hl7.fhir.r5.model.CodeSystem) outcome.getResources(0, 1).get(0);
}
return null; return null;
} }
@ -281,7 +270,7 @@ public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return txTemplate.execute(t-> { return txTemplate.execute(t -> {
Optional<TermConcept> codeOpt = findCode(theCodeSystem, theCode); Optional<TermConcept> codeOpt = findCode(theCodeSystem, theCode);
if (codeOpt.isPresent()) { if (codeOpt.isPresent()) {
TermConcept code = codeOpt.get(); TermConcept code = codeOpt.get();

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -8,10 +10,12 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
@ -28,16 +32,18 @@ import org.springframework.test.util.AopTestUtils;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat; import static org.junit.Assert.*;
import static org.junit.Assert.fail;
public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test { public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ValidateTest.class);
@Autowired @Autowired
private IValidatorModule myValidatorModule; private IValidatorModule myValidatorModule;
@Autowired
private IHapiTerminologySvc myTerminologySvc;
@Test @Test
public void testValidateStructureDefinition() throws Exception { public void testValidateStructureDefinition() throws Exception {
@ -106,6 +112,43 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
assertThat(ooString, containsString("Element '/f:Observation.device': minimum required = 1, but only found 0")); assertThat(ooString, containsString("Element '/f:Observation.device': minimum required = 1, but only found 0"));
} }
@Test
public void testValidateUsingExternallyDefinedCode() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl("http://foo");
codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
IIdType csId = myCodeSystemDao.create(codeSystem).getId();
TermCodeSystemVersion csv = new TermCodeSystemVersion();
csv.addConcept().setCode("bar").setDisplay("Bar Code");
myTerminologySvc.storeNewCodeSystemVersion(codeSystem, csv, mySrd, Collections.emptyList(), Collections.emptyList());
// Validate a resource containing this codesystem in a field with an extendable binding
Patient patient = new Patient();
patient.getText().setStatus(Narrative.NarrativeStatus.GENERATED).setDivAsString("<div>hello</div>");
patient
.addIdentifier()
.setSystem("http://example.com")
.setValue("12345")
.getType()
.addCoding()
.setSystem("http://foo")
.setCode("bar");
MethodOutcome outcome = myPatientDao.validate(patient, null, encode(patient), EncodingEnum.JSON, ValidationModeEnum.CREATE, null, mySrd);
IBaseOperationOutcome oo = outcome.getOperationOutcome();
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
// It would be ok for this to produce 0 issues, or just an information message too
assertEquals(1, OperationOutcomeUtil.getIssueCount(myFhirCtx, oo));
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code) (codes = http://foo#bar)", OperationOutcomeUtil.getFirstIssueDetails(myFhirCtx, oo));
}
private String encode(Patient thePatient) {
return myFhirCtx.newJsonParser().encodeResourceToString(thePatient);
}
private OperationOutcome doTestValidateResourceContainingProfileDeclaration(String methodName, EncodingEnum enc) throws IOException { private OperationOutcome doTestValidateResourceContainingProfileDeclaration(String methodName, EncodingEnum enc) throws IOException {
Bundle vss = loadResourceFromClasspath(Bundle.class, "/org/hl7/fhir/r4/model/valueset/valuesets.xml"); Bundle vss = loadResourceFromClasspath(Bundle.class, "/org/hl7/fhir/r4/model/valueset/valuesets.xml");
myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-status"), mySrd); myValueSetDao.update((ValueSet) findResourceByIdInBundle(vss, "observation-status"), mySrd);

View File

@ -98,13 +98,11 @@ public class ValidationSupportChain implements IValidationSupport {
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theSystem)) {
CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;
} }
} }
}
return null; return null;
} }

View File

@ -77,13 +77,11 @@ public class ValidationSupportChain implements IValidationSupport {
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theSystem)) {
CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;
} }
} }
}
return null; return null;
} }

View File

@ -75,13 +75,11 @@ public class ValidationSupportChain implements IValidationSupport {
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theSystem)) {
CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem);
if (retVal != null) { if (retVal != null) {
return retVal; return retVal;
} }
} }
}
return null; return null;
} }

View File

@ -218,6 +218,10 @@
handled by method implementations that did not have any <![CDATA[<code>@IncludeParam</code>]]> defined. This handled by method implementations that did not have any <![CDATA[<code>@IncludeParam</code>]]> defined. This
is now corrected. Thanks to Tuomo Ala-Vannesluoma for reporting and providing a test case! is now corrected. Thanks to Tuomo Ala-Vannesluoma for reporting and providing a test case!
</action> </action>
<action type="fix">
The JPA server failed to find codes defined in not-present codesystems in some cases, and reported
that the CodeSystem did not exist. This has been corrected.
</action>
</release> </release>
<release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)"> <release version="4.0.3" date="2019-09-03" description="Igloo (Point Release)">
<action type="fix"> <action type="fix">