Add setting to allow validation of reference targets (#1932)
* Add setting to allow validation of reference targets * Add changelog * Add changelog * License headers * Plugin version bump * Experiment with Maven build
This commit is contained in:
parent
e849ff2cbc
commit
67d363f9e1
|
@ -25,6 +25,16 @@ jobs:
|
||||||
inputs:
|
inputs:
|
||||||
targetType: 'inline'
|
targetType: 'inline'
|
||||||
script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER)
|
script: mkdir -p $(MAVEN_CACHE_FOLDER); pwd; ls -al $(MAVEN_CACHE_FOLDER)
|
||||||
|
- task: Maven@3
|
||||||
|
env:
|
||||||
|
JAVA_HOME_11_X64: /usr/local/openjdk-11
|
||||||
|
inputs:
|
||||||
|
goals: 'dependency:go-offline'
|
||||||
|
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
|
||||||
|
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
|
||||||
|
# These are JVM options (and don't show up in the build logs)
|
||||||
|
mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
|
||||||
|
jdkVersionOption: 1.11
|
||||||
- task: Maven@3
|
- task: Maven@3
|
||||||
env:
|
env:
|
||||||
JAVA_HOME_11_X64: /usr/local/openjdk-11
|
JAVA_HOME_11_X64: /usr/local/openjdk-11
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 1932
|
||||||
|
title: "A new configuration bean called ValidationSettings has been added to the JPA server. This can be used
|
||||||
|
to enable validation of reference target resources if necessary."
|
|
@ -4,6 +4,7 @@
|
||||||
title: "The version of a few dependencies have been bumped to the latest versions
|
title: "The version of a few dependencies have been bumped to the latest versions
|
||||||
(dependent HAPI modules listed in brackets):
|
(dependent HAPI modules listed in brackets):
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Guava (JPA): 28.2-jre -> 29.0-jre</li>
|
||||||
</ul>"
|
</ul>"
|
||||||
- item:
|
- item:
|
||||||
issue: "1862"
|
issue: "1862"
|
||||||
|
|
|
@ -32,7 +32,9 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
|
||||||
|
import ca.uhn.fhir.jpa.validation.JpaFhirInstanceValidator;
|
||||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
|
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
|
||||||
|
import ca.uhn.fhir.jpa.validation.ValidationSettings;
|
||||||
import ca.uhn.fhir.validation.IInstanceValidatorModule;
|
import ca.uhn.fhir.validation.IInstanceValidatorModule;
|
||||||
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
|
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
|
||||||
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
||||||
|
@ -88,12 +90,17 @@ public abstract class BaseConfigDstu3Plus extends BaseConfig {
|
||||||
@Bean(name = "myInstanceValidator")
|
@Bean(name = "myInstanceValidator")
|
||||||
@Lazy
|
@Lazy
|
||||||
public IInstanceValidatorModule instanceValidator() {
|
public IInstanceValidatorModule instanceValidator() {
|
||||||
FhirInstanceValidator val = new FhirInstanceValidator(fhirContext());
|
FhirInstanceValidator val = new JpaFhirInstanceValidator(fhirContext());
|
||||||
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
|
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
|
||||||
val.setValidationSupport(validationSupportChain());
|
val.setValidationSupport(validationSupportChain());
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ValidationSettings validationSettings() {
|
||||||
|
return new ValidationSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public abstract ITermReadSvc terminologyService();
|
public abstract ITermReadSvc terminologyService();
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package ca.uhn.fhir.jpa.validation;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2020 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||||
|
import org.hl7.fhir.exceptions.DefinitionException;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r5.elementmodel.Element;
|
||||||
|
import org.hl7.fhir.r5.elementmodel.JsonParser;
|
||||||
|
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class JpaFhirInstanceValidator extends FhirInstanceValidator {
|
||||||
|
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(JpaFhirInstanceValidator.class);
|
||||||
|
private final FhirContext myFhirContext;
|
||||||
|
@Autowired
|
||||||
|
private ValidationSettings myValidationSettings;
|
||||||
|
@Autowired
|
||||||
|
private DaoRegistry myDaoRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public JpaFhirInstanceValidator(FhirContext theFhirContext) {
|
||||||
|
super(theFhirContext);
|
||||||
|
myFhirContext = theFhirContext;
|
||||||
|
setValidatorResourceFetcher(new MyValidatorResourceFetcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyValidatorResourceFetcher implements IResourceValidator.IValidatorResourceFetcher {
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Override
|
||||||
|
public Element fetch(Object appContext, String theUrl) throws FHIRException {
|
||||||
|
|
||||||
|
IdType id = new IdType(theUrl);
|
||||||
|
String resourceType = id.getResourceType();
|
||||||
|
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
|
||||||
|
IBaseResource target;
|
||||||
|
try {
|
||||||
|
target = dao.read(id, (RequestDetails) appContext);
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
ourLog.info("Failed to resolve local reference: {}", theUrl);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new JsonParser(provideWorkerContext()).parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FHIRException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IResourceValidator.ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) {
|
||||||
|
int slashIdx = url.indexOf("/");
|
||||||
|
if (slashIdx > 0 && myFhirContext.getResourceTypes().contains(url.substring(0, slashIdx))) {
|
||||||
|
return myValidationSettings.getLocalReferenceValidationDefaultPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return IResourceValidator.ReferenceValidationPolicy.IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] fetchRaw(String url) throws IOException {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLocale(Locale locale) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package ca.uhn.fhir.jpa.validation;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2020 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||||
|
import org.thymeleaf.util.Validate;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class ValidationSettings {
|
||||||
|
|
||||||
|
private IResourceValidator.ReferenceValidationPolicy myLocalReferenceValidationDefaultPolicy = IResourceValidator.ReferenceValidationPolicy.IGNORE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
|
||||||
|
* <p>
|
||||||
|
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
|
||||||
|
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
|
||||||
|
* resource with that ID will be loaded from the database during validation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @since 5.1.0
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public IResourceValidator.ReferenceValidationPolicy getLocalReferenceValidationDefaultPolicy() {
|
||||||
|
return myLocalReferenceValidationDefaultPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies a default policy for validating local references. Default is {@literal IResourceValidator.ReferenceValidationPolicy.IGNORE}.
|
||||||
|
* <p>
|
||||||
|
* Note that this setting can have a measurable impact on validation performance, as it will cause reference targets
|
||||||
|
* to be resolved during validation. In other words, if a resource has a reference to (for example) "Patient/123", the
|
||||||
|
* resource with that ID will be loaded from the database during validation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @since 5.1.0
|
||||||
|
*/
|
||||||
|
public void setLocalReferenceValidationDefaultPolicy(@Nonnull IResourceValidator.ReferenceValidationPolicy theLocalReferenceValidationDefaultPolicy) {
|
||||||
|
Validate.notNull(theLocalReferenceValidationDefaultPolicy, "theLocalReferenceValidationDefaultPolicy must not be null");
|
||||||
|
myLocalReferenceValidationDefaultPolicy = theLocalReferenceValidationDefaultPolicy;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
|
||||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||||
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
|
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
|
||||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
|
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
|
||||||
|
import ca.uhn.fhir.jpa.validation.ValidationSettings;
|
||||||
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;
|
||||||
|
@ -35,6 +36,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
import org.hl7.fhir.r4.model.Group;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.Narrative;
|
import org.hl7.fhir.r4.model.Narrative;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
@ -42,6 +44,7 @@ import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome;
|
import org.hl7.fhir.r4.model.OperationOutcome;
|
||||||
import org.hl7.fhir.r4.model.Organization;
|
import org.hl7.fhir.r4.model.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
import org.hl7.fhir.r4.model.Questionnaire;
|
import org.hl7.fhir.r4.model.Questionnaire;
|
||||||
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
import org.hl7.fhir.r4.model.QuestionnaireResponse;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
@ -86,6 +89,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
||||||
private JpaValidationSupportChain myJpaValidationSupportChain;
|
private JpaValidationSupportChain myJpaValidationSupportChain;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myTransactionManager;
|
private PlatformTransactionManager myTransactionManager;
|
||||||
|
@Autowired
|
||||||
|
private ValidationSettings myValidationSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a loinc valueset that expands to more results than the expander is willing to do
|
* Create a loinc valueset that expands to more results than the expander is willing to do
|
||||||
|
@ -187,6 +192,200 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateProfileTargetType_PolicyCheckValid() throws IOException {
|
||||||
|
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_VALID);
|
||||||
|
|
||||||
|
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
|
||||||
|
myStructureDefinitionDao.create(profile, mySrd);
|
||||||
|
|
||||||
|
ValueSet vs = new ValueSet();
|
||||||
|
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
|
||||||
|
vs.getCompose().addInclude().setSystem("http://loinc.org");
|
||||||
|
myValueSetDao.create(vs);
|
||||||
|
|
||||||
|
CodeSystem cs = new CodeSystem();
|
||||||
|
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
|
||||||
|
cs.setUrl("http://loinc.org");
|
||||||
|
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
|
||||||
|
myCodeSystemDao.create(cs);
|
||||||
|
|
||||||
|
Group group = new Group();
|
||||||
|
group.setId("ABC");
|
||||||
|
group.setActive(true);
|
||||||
|
myGroupDao.update(group);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("DEF");
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
Practitioner practitioner = new Practitioner();
|
||||||
|
practitioner.setId("P");
|
||||||
|
practitioner.setActive(true);
|
||||||
|
myPractitionerDao.update(practitioner);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
|
||||||
|
obs.getText().setDivAsString("<div>Hello</div>");
|
||||||
|
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
|
||||||
|
obs.addPerformer(new Reference("Practitioner/P"));
|
||||||
|
obs.setEffective(DateTimeType.now());
|
||||||
|
obs.setStatus(ObservationStatus.FINAL);
|
||||||
|
obs.setValue(new StringType("This is the value"));
|
||||||
|
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||||
|
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
|
||||||
|
|
||||||
|
// Non-existent target
|
||||||
|
obs.setSubject(new Reference("Group/123"));
|
||||||
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of wrong type
|
||||||
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "Invalid Resource target type. Found Group, but expected one of ([Patient])", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of right type
|
||||||
|
obs.setSubject(new Reference("Patient/DEF"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateProfileTargetType_PolicyCheckExistsAndType() throws IOException {
|
||||||
|
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE);
|
||||||
|
|
||||||
|
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
|
||||||
|
myStructureDefinitionDao.create(profile, mySrd);
|
||||||
|
|
||||||
|
ValueSet vs = new ValueSet();
|
||||||
|
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
|
||||||
|
vs.getCompose().addInclude().setSystem("http://loinc.org");
|
||||||
|
myValueSetDao.create(vs);
|
||||||
|
|
||||||
|
CodeSystem cs = new CodeSystem();
|
||||||
|
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
|
||||||
|
cs.setUrl("http://loinc.org");
|
||||||
|
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
|
||||||
|
myCodeSystemDao.create(cs);
|
||||||
|
|
||||||
|
Group group = new Group();
|
||||||
|
group.setId("ABC");
|
||||||
|
group.setActive(true);
|
||||||
|
myGroupDao.update(group);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("DEF");
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
Practitioner practitioner = new Practitioner();
|
||||||
|
practitioner.setId("P");
|
||||||
|
practitioner.setActive(true);
|
||||||
|
myPractitionerDao.update(practitioner);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
|
||||||
|
obs.getText().setDivAsString("<div>Hello</div>");
|
||||||
|
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
|
||||||
|
obs.addPerformer(new Reference("Practitioner/P"));
|
||||||
|
obs.setEffective(DateTimeType.now());
|
||||||
|
obs.setStatus(ObservationStatus.FINAL);
|
||||||
|
obs.setValue(new StringType("This is the value"));
|
||||||
|
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||||
|
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
|
||||||
|
|
||||||
|
// Non-existent target
|
||||||
|
obs.setSubject(new Reference("Group/123"));
|
||||||
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of wrong type
|
||||||
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "Unable to find matching profile for Group/ABC (by type) among choices: ; [CanonicalType[http://hl7.org/fhir/StructureDefinition/Patient]]", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of right type
|
||||||
|
obs.setSubject(new Reference("Patient/DEF"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateProfileTargetType_PolicyCheckExists() throws IOException {
|
||||||
|
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.CHECK_EXISTS);
|
||||||
|
|
||||||
|
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
|
||||||
|
myStructureDefinitionDao.create(profile, mySrd);
|
||||||
|
|
||||||
|
ValueSet vs = new ValueSet();
|
||||||
|
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
|
||||||
|
vs.getCompose().addInclude().setSystem("http://loinc.org");
|
||||||
|
myValueSetDao.create(vs);
|
||||||
|
|
||||||
|
CodeSystem cs = new CodeSystem();
|
||||||
|
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
|
||||||
|
cs.setUrl("http://loinc.org");
|
||||||
|
cs.addConcept().setCode("123-4").setDisplay("Code 123 4");
|
||||||
|
myCodeSystemDao.create(cs);
|
||||||
|
|
||||||
|
Group group = new Group();
|
||||||
|
group.setId("ABC");
|
||||||
|
group.setActive(true);
|
||||||
|
myGroupDao.update(group);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId("DEF");
|
||||||
|
patient.setActive(true);
|
||||||
|
myPatientDao.update(patient);
|
||||||
|
|
||||||
|
Practitioner practitioner = new Practitioner();
|
||||||
|
practitioner.setId("P");
|
||||||
|
practitioner.setActive(true);
|
||||||
|
myPractitionerDao.update(practitioner);
|
||||||
|
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
|
||||||
|
obs.getText().setDivAsString("<div>Hello</div>");
|
||||||
|
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
|
||||||
|
obs.addPerformer(new Reference("Practitioner/P"));
|
||||||
|
obs.setEffective(DateTimeType.now());
|
||||||
|
obs.setStatus(ObservationStatus.FINAL);
|
||||||
|
obs.setValue(new StringType("This is the value"));
|
||||||
|
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
|
||||||
|
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("123-4").setDisplay("Display 3");
|
||||||
|
|
||||||
|
// Non-existent target
|
||||||
|
obs.setSubject(new Reference("Group/123"));
|
||||||
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of wrong type
|
||||||
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
// Target of right type
|
||||||
|
obs.setSubject(new Reference("Patient/DEF"));
|
||||||
|
oo = validateAndReturnOutcome(obs);
|
||||||
|
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||||
|
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
|
* Per: https://chat.fhir.org/#narrow/stream/179166-implementers/topic/Handling.20incomplete.20CodeSystems
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -743,6 +942,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
|
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
|
||||||
|
|
||||||
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(null);
|
BaseTermReadSvcImpl.setInvokeOnNextCallForUnitTest(null);
|
||||||
|
|
||||||
|
myValidationSettings.setLocalReferenceValidationDefaultPolicy(IResourceValidator.ReferenceValidationPolicy.IGNORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||||
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
|
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -197,6 +198,21 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
|
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
|
||||||
|
VersionSpecificWorkerContextWrapper wrappedWorkerContext = provideWorkerContext();
|
||||||
|
|
||||||
|
return new ValidatorWrapper()
|
||||||
|
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
|
||||||
|
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
|
||||||
|
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
|
||||||
|
.setExtensionDomains(getExtensionDomains())
|
||||||
|
.setNoTerminologyChecks(isNoTerminologyChecks())
|
||||||
|
.setValidatorResourceFetcher(getValidatorResourceFetcher())
|
||||||
|
.setAssumeValidRestReferences(isAssumeValidRestReferences())
|
||||||
|
.validate(wrappedWorkerContext, theValidationCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
protected VersionSpecificWorkerContextWrapper provideWorkerContext() {
|
||||||
VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
|
VersionSpecificWorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
|
||||||
if (wrappedWorkerContext == null) {
|
if (wrappedWorkerContext == null) {
|
||||||
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
|
VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter;
|
||||||
|
@ -213,7 +229,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
|
||||||
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical;
|
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) nonCanonical;
|
||||||
if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) {
|
if (valueSet.hasCodeSystem() && valueSet.getCodeSystem().hasSystem()) {
|
||||||
if (!valueSet.hasCompose()) {
|
if (!valueSet.hasCompose()) {
|
||||||
org.hl7.fhir.r5.model.ValueSet valueSetR5 = (org.hl7.fhir.r5.model.ValueSet) retVal;
|
ValueSet valueSetR5 = (ValueSet) retVal;
|
||||||
valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem());
|
valueSetR5.getCompose().addInclude().setSystem(valueSet.getCodeSystem().getSystem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,16 +273,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
|
||||||
wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter);
|
wrappedWorkerContext = new VersionSpecificWorkerContextWrapper(new ValidationSupportContext(myValidationSupport), converter);
|
||||||
}
|
}
|
||||||
myWrappedWorkerContext = wrappedWorkerContext;
|
myWrappedWorkerContext = wrappedWorkerContext;
|
||||||
|
return wrappedWorkerContext;
|
||||||
return new ValidatorWrapper()
|
|
||||||
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
|
|
||||||
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
|
|
||||||
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
|
|
||||||
.setExtensionDomains(getExtensionDomains())
|
|
||||||
.setNoTerminologyChecks(isNoTerminologyChecks())
|
|
||||||
.setValidatorResourceFetcher(getValidatorResourceFetcher())
|
|
||||||
.setAssumeValidRestReferences(isAssumeValidRestReferences())
|
|
||||||
.validate(wrappedWorkerContext, theValidationCtx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FhirContext getDstu2Context() {
|
private FhirContext getDstu2Context() {
|
||||||
|
|
4
pom.xml
4
pom.xml
|
@ -693,7 +693,7 @@
|
||||||
<!--<derby_version>10.15.1.3</derby_version>-->
|
<!--<derby_version>10.15.1.3</derby_version>-->
|
||||||
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
<error_prone_annotations_version>2.3.4</error_prone_annotations_version>
|
||||||
<error_prone_core_version>2.3.3</error_prone_core_version>
|
<error_prone_core_version>2.3.3</error_prone_core_version>
|
||||||
<guava_version>28.2-jre</guava_version>
|
<guava_version>29.0-jre</guava_version>
|
||||||
<gson_version>2.8.5</gson_version>
|
<gson_version>2.8.5</gson_version>
|
||||||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||||
<jaxb_api_version>2.3.1</jaxb_api_version>
|
<jaxb_api_version>2.3.1</jaxb_api_version>
|
||||||
|
@ -1669,7 +1669,7 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-antrun-plugin</artifactId>
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
<version>1.8</version>
|
<version>3.0.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|
Loading…
Reference in New Issue