Merge validator

This commit is contained in:
James Agnew 2018-05-09 17:22:34 -04:00
parent a0c40cf98a
commit adc73e75c9
32 changed files with 4934 additions and 3720 deletions

View File

@ -123,6 +123,19 @@ public class BundleUtil {
return null;
}
public static Integer getTotal(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("total");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
if (entries.size() > 0) {
IPrimitiveType<Number> typeElement = (IPrimitiveType<Number>) entries.get(0);
if (typeElement != null && typeElement.getValue() != null) {
return typeElement.getValue().intValue();
}
}
return null;
}
/**
* Extract all of the resources from a given bundle
*/

View File

@ -33,6 +33,11 @@
<type>jar</type>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-igpacks</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.cli;
*/
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
@ -31,12 +32,20 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import java.util.ArrayList;
import java.util.List;
public class LoadingValidationSupportDstu2 implements IValidationSupport {
private FhirContext myCtx = FhirContext.forDstu2Hl7Org();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoadingValidationSupportDstu2.class);
@Override
public List<StructureDefinition> allStructures() {
return new ArrayList<>();
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
return null;

View File

@ -21,21 +21,29 @@ package ca.uhn.fhir.cli;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu2;
import ca.uhn.fhir.igpacks.parser.IgPackParserDstu3;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.base.Charsets;
import com.phloc.commons.io.file.FileUtils;
import org.apache.commons.cli.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi.Color;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import static org.apache.commons.lang3.StringUtils.*;
import static org.fusesource.jansi.Ansi.ansi;
@ -71,10 +79,24 @@ public class ValidateCommand extends BaseCommand {
"Allow fetching remote resources (in other words, if a resource being validated refers to an external StructureDefinition, Questionnaire, etc. this flag allows the validator to access the internet to try and fetch this resource)");
retVal.addOption(new Option("l", "fetch-local", true, "Fetch a profile locally and use it if referenced"));
retVal.addOption("e", "encoding", false, "File encoding (default is UTF-8)");
addOptionalOption(retVal, null, "igpack", true, "If specified, provides the filename of an IGPack file to include in validation");
return retVal;
}
private String loadFile(String theFileName) throws ParseException {
return new String(loadFileAsByteArray(theFileName), Charsets.UTF_8);
}
private byte[] loadFileAsByteArray(String theFileName) throws ParseException {
byte[] input;
try {
input = IOUtils.toByteArray(new FileInputStream(new File(theFileName)));
} catch (IOException e) {
throw new ParseException("Failed to load file '" + theFileName + "' - Error: " + e.toString());
}
return input;
}
@Override
public void run(CommandLine theCommandLine) throws ParseException {
parseFhirContext(theCommandLine);
@ -112,16 +134,18 @@ public class ValidateCommand extends BaseCommand {
if (theCommandLine.hasOption("l")) {
String localProfile = theCommandLine.getOptionValue("l");
ourLog.info("Loading profile: {}", localProfile);
String input;
try {
input = IOUtils.toString(new FileReader(new File(localProfile)));
} catch (IOException e) {
throw new ParseException("Failed to load file '" + localProfile + "' - Error: " + e.toString());
}
String input = loadFile(localProfile);
localProfileResource = ca.uhn.fhir.rest.api.EncodingEnum.detectEncodingNoDefault(input).newParser(ctx).parseResource(input);
}
byte[] igPack = null;
String igpackFilename = null;
if (theCommandLine.hasOption("igpack")) {
igpackFilename = theCommandLine.getOptionValue("igpack");
igPack = loadFileAsByteArray(igpackFilename);
}
if (theCommandLine.hasOption("p")) {
switch (ctx.getVersion().getVersion()) {
case DSTU2: {
@ -129,6 +153,14 @@ public class ValidateCommand extends BaseCommand {
val.registerValidatorModule(instanceValidator);
org.hl7.fhir.instance.hapi.validation.ValidationSupportChain validationSupport = new org.hl7.fhir.instance.hapi.validation.ValidationSupportChain(
new org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport());
if (igPack != null) {
FhirContext hl7orgCtx = FhirContext.forDstu2Hl7Org();
hl7orgCtx.setParserErrorHandler(new LenientErrorHandler(false));
IgPackParserDstu2 parser = new IgPackParserDstu2(hl7orgCtx);
org.hl7.fhir.instance.hapi.validation.IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename);
validationSupport.addValidationSupport(igValidationSupport);
}
if (localProfileResource != null) {
org.hl7.fhir.instance.model.StructureDefinition convertedSd = FhirContext.forDstu2Hl7Org().newXmlParser().parseResource(org.hl7.fhir.instance.model.StructureDefinition.class, ctx.newXmlParser().encodeResourceToString(localProfileResource));
instanceValidator.setStructureDefintion(convertedSd);
@ -143,6 +175,12 @@ public class ValidateCommand extends BaseCommand {
FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
val.registerValidatorModule(instanceValidator);
ValidationSupportChain validationSupport = new ValidationSupportChain(new DefaultProfileValidationSupport());
if (igPack != null) {
IgPackParserDstu3 parser = new IgPackParserDstu3(getFhirContext());
IValidationSupport igValidationSupport = parser.parseIg(igPack, igpackFilename);
validationSupport.addValidationSupport(igValidationSupport);
}
if (localProfileResource != null) {
instanceValidator.setStructureDefintion((StructureDefinition) localProfileResource);
}
@ -193,7 +231,7 @@ public class ValidateCommand extends BaseCommand {
if (results.isSuccessful()) {
ourLog.info("Validation successful!");
} else {
ourLog.warn("Validation FAILED");
throw new CommandFailureException("Validation failed");
}
}
}

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.cli;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ValidateTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class);
@ -16,8 +18,43 @@ public class ValidateTest {
String resourcePath = ValidateTest.class.getResource("/patient-uslab-example1.xml").getFile();
ourLog.info(resourcePath);
App.main(new String[] {"validate", "-v", "dstu3", "-p", "-n", resourcePath});
App.main(new String[] {
"validate",
"-v", "dstu3",
"-p",
"-n", resourcePath});
}
@Test
public void testValidateUsingIgPackSucceedingDstu2() {
String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-good.json").getFile();
ourLog.info(resourcePath);
App.main(new String[] {
"validate",
"-v", "dstu2",
"-p",
"--igpack", "src/test/resources/argo-dstu2.pack",
"-n", resourcePath});
}
@Test
public void testValidateUsingIgPackFailingDstu2() {
String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-bad.json").getFile();
ourLog.info(resourcePath);
try {
App.main(new String[] {
"validate",
"-v", "dstu2",
"-p",
"--igpack", "src/test/resources/argo-dstu2.pack",
"-n", resourcePath});
// Should not get here
fail();
} catch (CommandFailureException e) {
// good
}
}
}

View File

@ -0,0 +1,37 @@
{
"resourceType": "Observation",
"id": "head-circumference",
"meta": {
"profile": [
"http://fhir.org/guides/argonaut/StructureDefinition/argo-vitalsigns"
]
},
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: head-circumference</p><p><b>meta</b>: </p><p><b>status</b>: final</p><p><b>category</b>: Vital Signs <span style=\"background: LightGoldenRodYellow\">(Details : {http://hl7.org/fhir/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})</span></p><p><b>code</b>: head_circumference <span style=\"background: LightGoldenRodYellow\">(Details : {LOINC code '8287-5' = 'Head Occipital-frontal circumference by Tape measure', given as 'head_circumference'})</span></p><p><b>subject</b>: <a href=\"Patient-peter-chalmers.html\">Generated Summary: id: peter-chalmers; Medical Record Number = 1032702; active; Peter James Chalmers (OFFICIAL); ph: (555)555-5555(WORK), person@example.org(WORK); gender: male; birthDate: 19/06/1964</a></p><p><b>encounter</b>: <a href=\"Encounter-691.html\">Generated Summary: id: 691; status: in-progress; class: inpatient</a></p><p><b>effective</b>: 12/08/2010</p><p><b>value</b>: 51.2 cm<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code cm = 'cm')</span></p></div>"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8287-5",
"display": "head_circumference"
}
],
"text": "head_circumference"
},
"subject": {
"reference": "Patient/peter-chalmers"
},
"encounter": {
"reference": "Encounter/691"
},
"effectiveDateTime": "2010-08-12",
"valueQuantity": {
"value": 51.2,
"unit": "cm",
"system": "http://unitsofmeasure.org",
"code": "cm"
}
}

View File

@ -0,0 +1,47 @@
{
"resourceType": "Observation",
"id": "head-circumference",
"meta": {
"profile": [
"http://fhir.org/guides/argonaut/StructureDefinition/argo-vitalsigns"
]
},
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: head-circumference</p><p><b>meta</b>: </p><p><b>status</b>: final</p><p><b>category</b>: Vital Signs <span style=\"background: LightGoldenRodYellow\">(Details : {http://hl7.org/fhir/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})</span></p><p><b>code</b>: head_circumference <span style=\"background: LightGoldenRodYellow\">(Details : {LOINC code '8287-5' = 'Head Occipital-frontal circumference by Tape measure', given as 'head_circumference'})</span></p><p><b>subject</b>: <a href=\"Patient-peter-chalmers.html\">Generated Summary: id: peter-chalmers; Medical Record Number = 1032702; active; Peter James Chalmers (OFFICIAL); ph: (555)555-5555(WORK), person@example.org(WORK); gender: male; birthDate: 19/06/1964</a></p><p><b>encounter</b>: <a href=\"Encounter-691.html\">Generated Summary: id: 691; status: in-progress; class: inpatient</a></p><p><b>effective</b>: 12/08/2010</p><p><b>value</b>: 51.2 cm<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code cm = 'cm')</span></p></div>"
},
"status": "final",
"category": {
"coding": [
{
"system": "http://hl7.org/fhir/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
],
"text": "Vital Signs"
},
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8287-5",
"display": "head_circumference"
}
],
"text": "head_circumference"
},
"subject": {
"reference": "Patient/peter-chalmers"
},
"encounter": {
"reference": "Encounter/691"
},
"effectiveDateTime": "2010-08-12",
"valueQuantity": {
"value": 51.2,
"unit": "cm",
"system": "http://unitsofmeasure.org",
"code": "cm"
}
}

View File

@ -0,0 +1,159 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.StopWatch;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ImplementationGuide;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseIgPackParser<T> {
private static final Logger ourLog = LoggerFactory.getLogger(BaseIgPackParser.class);
private final FhirContext myCtx;
public BaseIgPackParser(FhirContext theCtx) {
FhirVersionEnum expectedVersion = provideExpectedVersion();
Validate.isTrue(theCtx.getVersion().getVersion() == expectedVersion, "theCtx is not for the correct version, expecting " + expectedVersion);
myCtx = theCtx;
}
protected abstract T createValidationSupport(Map<IIdType, IBaseResource> theIgResources);
private IBaseResource findResource(Map<String, IBaseResource> theCandidateResources, IIdType theId) {
IBaseResource retVal = theCandidateResources.get(theId.toUnqualifiedVersionless().getValue());
if (retVal == null) {
throw new InternalErrorException("Unknown reference in ImplementationGuide: " + theId);
}
return retVal;
}
/**
* @param theIgInputStream The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public T parseIg(InputStream theIgInputStream, String theDescription) {
Validate.notNull(theIgInputStream, "theIdInputStream must not be null");
String igResourceName = "ImplementationGuide-ig.json";
ourLog.info("Parsing IGPack: {}", theDescription);
StopWatch sw = new StopWatch();
ZipInputStream zipInputStream = new ZipInputStream(theIgInputStream);
ZipEntry entry;
try {
Map<String, IBaseResource> candidateResources = new HashMap<>();
Map<IIdType, IBaseResource> igResources = new HashMap<>();
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().endsWith(".json")) {
IBaseResource parsed;
InputStreamReader nextReader = new InputStreamReader(zipInputStream, Constants.CHARSET_UTF8);
if (entry.getName().equals(igResourceName)) {
parsed = FhirContext.forDstu3().newJsonParser().parseResource(ImplementationGuide.class, nextReader);
} else {
parsed = myCtx.newJsonParser().parseResource(nextReader);
}
candidateResources.put(entry.getName(), parsed);
}
}
ourLog.info("Parsed {} candidateResources in {}ms", candidateResources.size(), sw.getMillis());
ImplementationGuide ig = (ImplementationGuide) candidateResources.get(igResourceName);
if (ig == null) {
throw new InternalErrorException("IG Pack '" + theDescription + "' does not contain a resource named: " + igResourceName);
}
HashMap<String, IBaseResource> newCandidateResources = new HashMap<>();
for (IBaseResource next : candidateResources.values()) {
newCandidateResources.put(next.getIdElement().toUnqualifiedVersionless().getValue(), next);
}
candidateResources = newCandidateResources;
for (ImplementationGuide.ImplementationGuidePackageComponent nextPackage : ig.getPackage()) {
ourLog.info("Processing package {}", nextPackage.getName());
for (ImplementationGuide.ImplementationGuidePackageResourceComponent nextResource : nextPackage.getResource()) {
if (isNotBlank(nextResource.getSourceReference().getReference())) {
IdType id = new IdType(nextResource.getSourceReference().getReference());
if (isNotBlank(id.getResourceType())) {
switch (id.getResourceType()) {
case "CodeSystem":
case "ConceptMap":
case "StructureDefinition":
case "ValueSet":
IBaseResource resource = findResource(candidateResources, id);
igResources.put(id.toUnqualifiedVersionless(), resource);
break;
}
}
}
}
}
ourLog.info("IG contains {} resources", igResources.size());
return createValidationSupport(igResources);
} catch (Exception e) {
throw new InternalErrorException("Failure while parsing IG: " + e, e);
}
}
/**
* @param theIgPack The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public T parseIg(byte[] theIgPack, String theDescription) {
return parseIg(new ByteArrayInputStream(theIgPack), theDescription);
}
protected abstract FhirVersionEnum provideExpectedVersion();
}

View File

@ -0,0 +1,54 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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.context.FhirVersionEnum;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Map;
public class IgPackParserDstu2 extends BaseIgPackParser<IValidationSupport> {
public IgPackParserDstu2(FhirContext theCtx) {
super(massage(theCtx));
}
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu2(theIgResources);
}
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU2_HL7ORG;
}
private static FhirContext massage(FhirContext theCtx) {
if (theCtx.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
return FhirContext.forDstu2Hl7Org();
} else {
return theCtx;
}
}
}

View File

@ -23,119 +23,26 @@ package ca.uhn.fhir.igpacks.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ImplementationGuide;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class IgPackParserDstu3 {
private static final Logger ourLog = LoggerFactory.getLogger(IgPackParserDstu3.class);
private final FhirContext myCtx;
public class IgPackParserDstu3 extends BaseIgPackParser<IValidationSupport> {
public IgPackParserDstu3(FhirContext theCtx) {
FhirVersionEnum expectedVersion = FhirVersionEnum.DSTU3;
Validate.isTrue(theCtx.getVersion().getVersion() == expectedVersion, "theCtx is not for the correct version, expecting " + expectedVersion);
myCtx = theCtx;
super(theCtx);
}
private IBaseResource findResource(Map<String, IBaseResource> theCandidateResources, IIdType theId) {
IBaseResource retVal = theCandidateResources.get(theId.toUnqualifiedVersionless().getValue());
if (retVal == null) {
throw new InternalErrorException("Unknown reference in ImplementationGuide: " + theId);
}
return retVal;
protected IValidationSupport createValidationSupport(Map<IIdType, IBaseResource> theIgResources) {
return new IgPackValidationSupportDstu3(theIgResources);
}
/**
* @param theIgInputStream The "validator.pack" ZIP file
* @param theDescription A description (just used for logs)
*/
public IValidationSupport parseIg(InputStream theIgInputStream, String theDescription) {
Validate.notNull(theIgInputStream, "theIdInputStream must not be null");
ourLog.info("Parsing IGPack: {}", theDescription);
StopWatch sw = new StopWatch();
ZipInputStream zipInputStream = new ZipInputStream(theIgInputStream);
ZipEntry entry;
try {
Map<String, IBaseResource> candidateResources = new HashMap<>();
Map<IIdType, IBaseResource> igResources = new HashMap<>();
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().endsWith(".json")) {
InputStreamReader nextReader = new InputStreamReader(zipInputStream, Constants.CHARSET_UTF8);
IBaseResource parsed = myCtx.newJsonParser().parseResource(nextReader);
candidateResources.put(entry.getName(), parsed);
}
}
ourLog.info("Parsed {} candidateResources in {}ms", candidateResources.size(), sw.getMillis());
String igResourceName = "ImplementationGuide-ig.json";
ImplementationGuide ig = (ImplementationGuide) candidateResources.get(igResourceName);
if (ig == null) {
throw new InternalErrorException("IG Pack '" + theDescription + "' does not contain a resource named: " + igResourceName);
}
// ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ig));
HashMap<String, IBaseResource> newCandidateResources = new HashMap<>();
for (IBaseResource next : candidateResources.values()) {
newCandidateResources.put(next.getIdElement().toUnqualifiedVersionless().getValue(), next);
}
candidateResources = newCandidateResources;
for (ImplementationGuide.ImplementationGuidePackageComponent nextPackage : ig.getPackage()) {
ourLog.info("Processing package {}", nextPackage.getName());
for (ImplementationGuide.ImplementationGuidePackageResourceComponent nextResource : nextPackage.getResource()) {
if (isNotBlank(nextResource.getSourceReference().getReference())) {
IdType id = new IdType(nextResource.getSourceReference().getReference());
if (isNotBlank(id.getResourceType())) {
switch (id.getResourceType()) {
case "CodeSystem":
case "ConceptMap":
case "StructureDefinition":
case "ValueSet":
IBaseResource resource = findResource(candidateResources, id);
igResources.put(id.toUnqualifiedVersionless(), resource);
break;
}
}
}
}
}
ourLog.info("IG contains {} resources", igResources.size());
return new IgPackValidationSupportDstu3(igResources);
} catch (Exception e) {
throw new InternalErrorException("Failure while parsing IG: " + e);
}
protected FhirVersionEnum provideExpectedVersion() {
return FhirVersionEnum.DSTU3;
}
}

View File

@ -0,0 +1,115 @@
package ca.uhn.fhir.igpacks.parser;
/*-
* #%L
* hapi-fhir-igpacks
* %%
* Copyright (C) 2014 - 2018 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 org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.ConceptMap;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class IgPackValidationSupportDstu2 implements IValidationSupport {
private final Map<IIdType, IBaseResource> myIgResources;
public IgPackValidationSupportDstu2(Map<IIdType, IBaseResource> theIgResources) {
myIgResources = theIgResources;
}
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (next.getKey().getResourceType().equals("StructureDefinition")) {
retVal.add((StructureDefinition) next.getValue());
}
}
return retVal;
}
@Override
public ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, ValueSet.ConceptSetComponent theInclude) {
return null;
}
@Override
public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) {
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (next.getKey().getResourceType().equals("ValueSet")) {
ValueSet nextVs = (ValueSet) next.getValue();
if (theSystem.equals(nextVs.getUrl())) {
return nextVs;
}
}
}
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
for (Map.Entry<IIdType, IBaseResource> next : myIgResources.entrySet()) {
if (theClass.equals(ConceptMap.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
ConceptMap sd = ((ConceptMap) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
if (theClass.equals(StructureDefinition.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
StructureDefinition sd = ((StructureDefinition) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
if (theClass.equals(ValueSet.class)) {
if (theClass.isAssignableFrom(next.getValue().getClass())) {
ValueSet sd = ((ValueSet) next.getValue());
if (sd.getUrl().equals(theUri)) {
return (T) sd;
}
}
}
}
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
return false;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return null;
}
}

Binary file not shown.

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -74,7 +75,7 @@ public class BaseDstu2Config extends BaseConfig {
@Lazy
public IValidatorModule instanceValidatorDstu2() {
FhirInstanceValidator retVal = new FhirInstanceValidator();
retVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
retVal.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
retVal.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), jpaValidationSupportDstu2()));
return retVal;
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
@ -15,6 +16,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import java.util.ArrayList;
import java.util.List;
/*
* #%L
@ -61,6 +64,11 @@ public class JpaValidationSupportDstu2 implements IJpaValidationSupportDstu2 {
@Qualifier("myFhirContextDstu2")
private FhirContext myDstu2Ctx;
@Override
public List<StructureDefinition> allStructures() {
return new ArrayList<>();
}
@Override
@Transactional(value = TxType.SUPPORTS)
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {

View File

@ -23,6 +23,9 @@ package ca.uhn.fhir.rest.server.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -32,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* This class is a simple implementation of the resource provider
@ -56,6 +60,11 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
private final String myResourceName;
private Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
private long myNextId;
private AtomicLong myDeleteCount = new AtomicLong(0);
private AtomicLong mySearchCount = new AtomicLong(0);
private AtomicLong myUpdateCount = new AtomicLong(0);
private AtomicLong myCreateCount = new AtomicLong(0);
private AtomicLong myReadCount = new AtomicLong(0);
/**
* Constructor
@ -79,6 +88,17 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
myIdToVersionToResourceMap.clear();
}
/**
* Clear the counts used by {@link #getCountRead()} and other count methods
*/
public void clearCounts() {
myReadCount.set(0L);
myUpdateCount.set(0L);
myCreateCount.set(0L);
myDeleteCount.set(0L);
mySearchCount.set(0L);
}
@Create
public MethodOutcome create(@ResourceParam T theResource) {
long idPart = myNextId++;
@ -87,6 +107,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
IIdType id = store(theResource, idPartAsString, versionIdPart);
myCreateCount.incrementAndGet();
return new MethodOutcome()
.setCreated(true)
.setId(id);
@ -99,13 +121,56 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
throw new ResourceNotFoundException(theId);
}
long nextVersion = versions.lastEntry().getKey() + 1L;
IIdType id = store(null, theId.getIdPart(), nextVersion);
myDeleteCount.incrementAndGet();
return new MethodOutcome()
.setId(id);
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountCreate() {
return myCreateCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountDelete() {
return myDeleteCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountRead() {
return myReadCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountSearch() {
return mySearchCount.get();
}
/**
* This method returns a simple operation count. This is mostly
* useful for testing purposes.
*/
public long getCountUpdate() {
return myUpdateCount.get();
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return myResourceType;
@ -113,7 +178,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
private synchronized TreeMap<Long, T> getVersionToResource(String theIdPart) {
if (!myIdToVersionToResourceMap.containsKey(theIdPart)) {
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<Long, T>());
myIdToVersionToResourceMap.put(theIdPart, new TreeMap<>());
}
return myIdToVersionToResourceMap.get(theIdPart);
}
@ -125,6 +190,7 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
throw new ResourceNotFoundException(theId);
}
T retVal;
if (theId.hasVersionIdPart()) {
Long versionId = theId.getVersionIdPartAsLong();
if (!versions.containsKey(versionId)) {
@ -134,23 +200,52 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
if (resource == null) {
throw new ResourceGoneException(theId);
}
return resource;
retVal = resource;
}
} else {
return versions.lastEntry().getValue();
retVal = versions.lastEntry().getValue();
}
myReadCount.incrementAndGet();
return retVal;
}
@Search
public List<IBaseResource> search() {
public List<IBaseResource> search(
@OptionalParam(name = "_id") TokenAndListParam theIds) {
List<IBaseResource> retVal = new ArrayList<>();
for (TreeMap<Long, T> next : myIdToVersionToResourceMap.values()) {
if (next.isEmpty() == false) {
retVal.add(next.lastEntry().getValue());
T nextResource = next.lastEntry().getValue();
boolean matches = true;
if (theIds != null && theIds.getValuesAsQueryTokens().size() > 0) {
for (TokenOrListParam nextIdAnd : theIds.getValuesAsQueryTokens()) {
matches = false;
for (TokenParam nextOr : nextIdAnd.getValuesAsQueryTokens()) {
if (nextOr.getValue().equals(nextResource.getIdElement().getIdPart())) {
matches = true;
}
}
if (!matches) {
break;
}
}
}
if (!matches) {
continue;
}
retVal.add(nextResource);
}
}
mySearchCount.incrementAndGet();
return retVal;
}
@ -188,6 +283,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
IIdType id = store(theResource, idPartAsString, versionIdPart);
myUpdateCount.incrementAndGet();
return new MethodOutcome()
.setCreated(created)
.setId(id);

View File

@ -140,6 +140,8 @@ public class ContactPoint extends Type implements ICompositeType {
return ContactPointSystem.PAGER;
if ("other".equals(codeString))
return ContactPointSystem.OTHER;
if ("url".equals(codeString))
return ContactPointSystem.OTHER;
throw new IllegalArgumentException("Unknown ContactPointSystem code '"+codeString+"'");
}
public Enumeration<ContactPointSystem> fromType(Base code) throws FHIRException {

View File

@ -118,6 +118,8 @@ public class StructureDefinition extends DomainResource {
return StructureDefinitionKind.RESOURCE;
if ("logical".equals(codeString))
return StructureDefinitionKind.LOGICAL;
if ("complex-type".equals(codeString))
return StructureDefinitionKind.DATATYPE;
throw new IllegalArgumentException("Unknown StructureDefinitionKind code '"+codeString+"'");
}
public Enumeration<StructureDefinitionKind> fromType(Base code) throws FHIRException {

View File

@ -21,7 +21,10 @@ import org.junit.BeforeClass;
import org.junit.Test;
import javax.servlet.ServletException;
import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
@ -31,11 +34,14 @@ public class HashMapResourceProviderTest {
private static Server ourListenerServer;
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static HashMapResourceProvider<Patient> myPatientResourceProvider;
private static HashMapResourceProvider<Observation> myObservationResourceProvider;
@Before
public void before() {
ourRestServer.clearData();
myPatientResourceProvider.clearCounts();
myObservationResourceProvider.clearCounts();
}
@Test
@ -50,6 +56,8 @@ public class HashMapResourceProviderTest {
// Read
p = (Patient) ourClient.read().resource("Patient").withId(id).execute();
assertEquals(true, p.getActive());
assertEquals(1, myPatientResourceProvider.getCountRead());
}
@Test
@ -76,8 +84,12 @@ public class HashMapResourceProviderTest {
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("1", id.getVersionIdPart());
assertEquals(0, myPatientResourceProvider.getCountDelete());
ourClient.delete().resourceById(id.toUnqualifiedVersionless()).execute();
assertEquals(1, myPatientResourceProvider.getCountDelete());
// Read
ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
try {
@ -103,6 +115,56 @@ public class HashMapResourceProviderTest {
Bundle resp = ourClient.search().forResource("Patient").returnBundle(Bundle.class).execute();
assertEquals(100, resp.getTotal());
assertEquals(100, resp.getEntry().size());
assertEquals(1, myPatientResourceProvider.getCountSearch());
}
@Test
public void testSearchById() {
// Create
for (int i = 0; i < 100; i++) {
Patient p = new Patient();
p.addName().setFamily("FAM" + i);
IIdType id = ourClient.create().resource(p).execute().getId();
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("1", id.getVersionIdPart());
}
// Search
Bundle resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.returnBundle(Bundle.class).execute();
assertEquals(2, resp.getTotal());
assertEquals(2, resp.getEntry().size());
List<String> respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/2", "Patient/3"));
// Search
resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.where(Patient.RES_ID.exactly().codes("2", "3"))
.returnBundle(Bundle.class).execute();
assertEquals(2, resp.getTotal());
assertEquals(2, resp.getEntry().size());
respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/2", "Patient/3"));
resp = ourClient
.search()
.forResource("Patient")
.where(Patient.RES_ID.exactly().codes("2", "3"))
.where(Patient.RES_ID.exactly().codes("4", "3"))
.returnBundle(Bundle.class).execute();
respIds = resp.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(respIds, containsInAnyOrder("Patient/3"));
assertEquals(1, resp.getTotal());
assertEquals(1, resp.getEntry().size());
}
@Test
@ -122,6 +184,9 @@ public class HashMapResourceProviderTest {
assertThat(id.getIdPart(), matchesPattern("[0-9]+"));
assertEquals("2", id.getVersionIdPart());
assertEquals(1, myPatientResourceProvider.getCountCreate());
assertEquals(1, myPatientResourceProvider.getCountUpdate());
// Read
p = (Patient) ourClient.read().resource("Patient").withId(id.withVersion("1")).execute();
assertEquals(true, p.getActive());
@ -147,6 +212,8 @@ public class HashMapResourceProviderTest {
ourRestServer = new MyRestfulServer();
String ourBase = "http://localhost:" + ourListenerPort + "/";
ourListenerServer = new Server(ourListenerPort);
ourCtx.getRestfulClientFactory().setSocketTimeout(120000);
ourClient = ourCtx.newRestfulGenericClient(ourBase);
ServletContextHandler proxyHandler = new ServletContextHandler();
@ -161,6 +228,7 @@ public class HashMapResourceProviderTest {
}
private static class MyRestfulServer extends RestfulServer {
MyRestfulServer() {
super(ourCtx);
}
@ -177,8 +245,10 @@ public class HashMapResourceProviderTest {
protected void initialize() throws ServletException {
super.initialize();
registerProvider(new HashMapResourceProvider<>(ourCtx, Patient.class));
registerProvider(new HashMapResourceProvider<>(ourCtx, Observation.class));
myPatientResourceProvider = new HashMapResourceProvider<>(ourCtx, Patient.class);
myObservationResourceProvider = new HashMapResourceProvider<>(ourCtx, Observation.class);
registerProvider(myPatientResourceProvider);
registerProvider(myObservationResourceProvider);
}

View File

@ -25,6 +25,19 @@ public class BundleUtilTest {
Assert.assertEquals(null, BundleUtil.getLinkUrlOfType(ourCtx, b, "next"));
}
@Test
public void testGetTotal() {
Bundle b = new Bundle();
b.setTotal(999);
Assert.assertEquals(999, BundleUtil.getTotal(ourCtx, b).intValue());
}
@Test
public void testGetTotalNull() {
Bundle b = new Bundle();
Assert.assertEquals(null, BundleUtil.getTotal(ourCtx, b));
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -3,11 +3,8 @@ package org.hl7.fhir.instance.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.Bundle;
import org.hl7.fhir.instance.model.*;
import org.hl7.fhir.instance.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.instance.model.CodeType;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -16,8 +13,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -25,6 +21,13 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private Map<String, ValueSet> myDefaultValueSets;
private Map<String, ValueSet> myCodeSystems;
private static final Set<String> ourResourceNames;
private static final FhirContext ourHl7OrgCtx;
static {
ourHl7OrgCtx = FhirContext.forDstu2Hl7Org();
ourResourceNames = FhirContext.forDstu2().getResourceNames();
}
/**
* Constructor
@ -33,6 +36,18 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
super();
}
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (String next : ourResourceNames) {
StructureDefinition profile = FhirInstanceValidator.loadProfileOrReturnNull(null, ourHl7OrgCtx, next);
retVal.add(profile);
}
return retVal;
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
return null;
@ -43,7 +58,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
synchronized (this) {
Map<String, ValueSet> valueSets = myCodeSystems;
if (valueSets == null) {
valueSets = new HashMap<String, ValueSet>();
valueSets = new HashMap<>();
loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/valuesets.xml");
loadValueSets(theContext, valueSets, "/org/hl7/fhir/instance/model/valueset/v2-tables.xml");
@ -78,7 +93,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
throw new InternalErrorException("UTF-8 encoding not supported on this platform", e);
}
defaultValueSets = new HashMap<String, ValueSet>();
defaultValueSets = new HashMap<>();
FhirContext ctx = FhirInstanceValidator.getHl7OrgDstu2Ctx(theContext);
Bundle bundle = ctx.newXmlParser().parseResource(Bundle.class, reader);

View File

@ -1,45 +1,73 @@
package org.hl7.fhir.instance.hapi.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.io.*;
import java.util.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.instance.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import com.google.gson.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.convertors.VersionConvertorAdvisor40;
import org.hl7.fhir.convertors.VersionConvertor_10_40;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.instance.model.*;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.formats.IParser;
import org.hl7.fhir.r4.formats.ParserType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.hl7.fhir.r4.utils.INarrativeGenerator;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.r4.validation.InstanceValidator;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule {
private static FhirContext ourHl7OrgCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private static final FhirContext FHIR_CONTEXT = FhirContext.forDstu2();
private static FhirContext ourHl7OrgCtx;
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
private volatile WorkerContextWrapper myWrappedWorkerContext;
private VersionConvertorAdvisor40 myAdvisor = new NullAdvisor();
/**
* Constructor
*
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport
* validation support}
* <p>
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
@ -48,8 +76,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
/**
* Constructor which uses the given validation support
*
* @param theValidationSupport
* The validation support
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
@ -57,6 +84,35 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport;
}
private CodeSystem convertCodeSystem(ValueSet theFetched) {
CodeSystem retVal = new CodeSystem();
retVal.setUrl(theFetched.getCodeSystem().getSystem());
retVal.setVersion(theFetched.getVersion());
List<ValueSet.ConceptDefinitionComponent> sourceConceptList = theFetched.getCodeSystem().getConcept();
List<CodeSystem.ConceptDefinitionComponent> targetConceptList = retVal.getConcept();
convertConceptList(sourceConceptList, targetConceptList);
return retVal;
}
private CodeSystem.ConceptDefinitionComponent convertConceptDefinition(ValueSet.ConceptDefinitionComponent next) {
CodeSystem.ConceptDefinitionComponent convertedConceptDef = new CodeSystem.ConceptDefinitionComponent();
convertedConceptDef.setCode(next.getCode());
convertedConceptDef.setDisplay(next.getDisplay());
convertConceptList(next.getConcept(), convertedConceptDef.getConcept());
return convertedConceptDef;
}
private void convertConceptList(List<ValueSet.ConceptDefinitionComponent> theSourceConceptList, List<CodeSystem.ConceptDefinitionComponent> theTargetConceptList) {
for (ValueSet.ConceptDefinitionComponent next : theSourceConceptList) {
CodeSystem.ConceptDefinitionComponent convertedConceptDef = convertConceptDefinition(next);
theTargetConceptList.add(convertedConceptDef);
}
}
private String determineResourceName(Document theDocument) {
Element root = null;
@ -71,15 +127,18 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
return root.getLocalName();
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchResource(theCtx, StructureDefinition.class, sdName);
return profile;
}
/**
* Returns the "best practice" warning level (default is
* {@link BestPracticeWarningLevel#Hint}).
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <p>
* The FHIR Instance Validator has a number of checks for best practices in
* terms of FHIR usage. If this setting is set to
* {@link BestPracticeWarningLevel#Error}, any resource data which does not
* meet these best practices will be reported at the ERROR level. If this
* setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
@ -90,67 +149,98 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
/**
* Returns the {@link IValidationSupport validation support} in use by this
* validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for
* this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
/**
* Sets the "best practice warning level". When validating, any deviations
* from best practices will be reported at this level.
* Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
* this level.
* <p>
* The FHIR Instance Validator has a number of checks for best practices in
* terms of FHIR usage. If this setting is set to
* {@link BestPracticeWarningLevel#Error}, any resource data which does not
* meet these best practices will be reported at the ERROR level. If this
* setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @param theBestPracticeWarningLevel
* The level, must not be <code>null</code>
* @param theBestPracticeWarningLevel The level, must not be <code>null</code>
*/
public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
Validate.notNull(theBestPracticeWarningLevel);
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
}
/**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
/**
* Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
myWrappedWorkerContext = null;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
* validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) {
noTerminologyChecks = theNoTerminologyChecks;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
}
/**
* Sets the {@link IValidationSupport validation support} in use by this
* validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for
* this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
org.hl7.fhir.instance.validation.InstanceValidator v;
WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
wrappedWorkerContext = new WorkerContextWrapper(workerContext);
}
myWrappedWorkerContext = wrappedWorkerContext;
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new org.hl7.fhir.instance.validation.InstanceValidator(workerContext);
v = new InstanceValidator(wrappedWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
// v.setShouldCheckForIdPresence(false);
// v.setRequireResourceId(false);
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(true);
v.setSuppressLoincSnomedMessages(true);
v.setNoTerminologyChecks(isNoTerminologyChecks());
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) {
Document document;
@ -167,10 +257,10 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
String resourceName = determineResourceName(document);
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : loadProfileOrReturnNull(messages, theCtx, resourceName);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(messages, document, profile);
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
@ -180,10 +270,10 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
JsonObject json = gson.fromJson(theInput, JsonObject.class);
String resourceName = json.get("resourceType").getAsString();
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : loadProfileOrReturnNull(messages, theCtx, resourceName);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(messages, json, profile);
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
@ -198,6 +288,10 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
messages.remove(i);
i--;
}
if (next.getLocation().contains("text")) {
messages.remove(i);
i--;
}
}
return messages;
}
@ -232,8 +326,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
String profileClasspath = theCtx.getVersion().getPathToSchemaDefinitions().replace("/schema", "/profile");
String profileCpName = profileClasspath + '/' + theResourceName.toLowerCase() + ".profile.xml";
String profileText;
try {
InputStream inputStream = FhirInstanceValidator.class.getResourceAsStream(profileCpName);
try (InputStream inputStream = FhirInstanceValidator.class.getResourceAsStream(profileCpName)) {
if (inputStream == null) {
if (theMessages != null) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL)
@ -256,4 +349,472 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
return profile;
}
private class WorkerContextWrapper implements IWorkerContext {
private final HapiWorkerContext myWrap;
private final VersionConvertor_10_40 myConverter;
private volatile List<org.hl7.fhir.r4.model.StructureDefinition> myAllStructures;
private LoadingCache<ResourceKey, org.hl7.fhir.r4.model.Resource> myFetchResourceCache
= Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(10000)
.build(new CacheLoader<ResourceKey, org.hl7.fhir.r4.model.Resource>() {
@Override
public org.hl7.fhir.r4.model.Resource load(FhirInstanceValidator.ResourceKey key) throws Exception {
org.hl7.fhir.instance.model.Resource fetched;
switch (key.getResourceName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri());
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "CodeSystem":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
if (fetched == null) {
if (key.getUri().equals("http://hl7.org/fhir/StructureDefinition/xhtml")) {
return null;
}
}
try {
org.hl7.fhir.r4.model.Resource converted = new VersionConvertor_10_40(myAdvisor).convertResource(fetched);
if (fetched instanceof StructureDefinition) {
StructureDefinition fetchedSd = (StructureDefinition) fetched;
StructureDefinition.StructureDefinitionKind kind = fetchedSd.getKind();
if (kind == StructureDefinition.StructureDefinitionKind.DATATYPE) {
BaseRuntimeElementDefinition<?> element = FHIR_CONTEXT.getElementDefinition(fetchedSd.getName());
if (element instanceof RuntimePrimitiveDatatypeDefinition) {
org.hl7.fhir.r4.model.StructureDefinition convertedSd = (org.hl7.fhir.r4.model.StructureDefinition) converted;
convertedSd.setKind(org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE);
}
}
}
return converted;
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
});
public WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext;
myConverter = new VersionConvertor_10_40(myAdvisor);
}
@Override
public List<org.hl7.fhir.r4.model.MetadataResource> allConformanceResources() {
throw new UnsupportedOperationException();
}
@Override
public List<org.hl7.fhir.r4.model.StructureDefinition> allStructures() {
List<org.hl7.fhir.r4.model.StructureDefinition> retVal = myAllStructures;
if (retVal == null) {
retVal = new ArrayList<>();
for (StructureDefinition next : myWrap.allStructures()) {
try {
org.hl7.fhir.r4.model.StructureDefinition converted = new VersionConvertor_10_40(myAdvisor).convertStructureDefinition(next);
if (converted != null) {
retVal.add(converted);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
myAllStructures = retVal;
}
return retVal;
}
@Override
public void cacheResource(org.hl7.fhir.r4.model.Resource res) throws FHIRException {
throw new UnsupportedOperationException();
}
private ValidationResult convertValidationResult(org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult theResult) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
conceptDefinition = convertConceptDefinition(theResult.asConceptDefinition());
}
ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
return retVal;
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
ValueSet convertedSource = null;
try {
convertedSource = new VersionConvertor_10_40(myAdvisor).convertValueSet(source);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.instance.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk);
org.hl7.fhir.r4.model.ValueSet convertedResult = null;
if (expanded.getValueset() != null) {
try {
convertedResult = new VersionConvertor_10_40(myAdvisor).convertValueSet(expanded.getValueset());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
String error = expanded.getError();
ValueSetExpander.TerminologyServiceErrorClass result = null;
return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result);
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException {
throw new UnsupportedOperationException();
}
@Override
public org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandVS(org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException {
ValueSet.ConceptSetComponent convertedInc = null;
if (inc != null) {
try {
convertedInc = new VersionConvertor_10_40(myAdvisor).convertConceptSetComponent(inc);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
ValueSet.ValueSetExpansionComponent expansion = myWrap.expandVS(convertedInc);
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent retVal = null;
if (expansion != null) {
try {
retVal = new VersionConvertor_10_40(myAdvisor).convertValueSetExpansionComponent(expansion);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
return retVal;
}
@Override
public org.hl7.fhir.r4.model.CodeSystem fetchCodeSystem(String system) {
ValueSet fetched = myWrap.fetchCodeSystem(system);
if (fetched == null) {
return null;
}
return convertCodeSystem(fetched);
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> class_, String uri) {
ResourceKey key = new ResourceKey(class_.getSimpleName(), uri);
@SuppressWarnings("unchecked")
T retVal = (T) myFetchResourceCache.get(key);
return retVal;
}
@Override
public org.hl7.fhir.r4.model.Resource fetchResourceById(String type, String uri) {
throw new UnsupportedOperationException();
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException {
T retVal = fetchResource(class_, uri);
if (retVal == null) {
throw new FHIRException("Can not find resource of type " + class_.getSimpleName() + " with uri " + uri);
}
return retVal;
}
@Override
public List<org.hl7.fhir.r4.model.ConceptMap> findMapsForSource(String url) {
throw new UnsupportedOperationException();
}
@Override
public String getAbbreviation(String name) {
return myWrap.getAbbreviation(name);
}
public VersionConvertor_10_40 getConverter() {
return myConverter;
}
@Override
public org.hl7.fhir.r4.model.ExpansionProfile getExpansionProfile() {
throw new UnsupportedOperationException();
}
@Override
public void setExpansionProfile(org.hl7.fhir.r4.model.ExpansionProfile expProfile) {
throw new UnsupportedOperationException();
}
@Override
public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) {
throw new UnsupportedOperationException();
}
@Override
public IParser getParser(ParserType type) {
throw new UnsupportedOperationException();
}
@Override
public IParser getParser(String type) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getResourceNames() {
return myWrap.getResourceNames();
}
@Override
public Set<String> getResourceNamesAsSet() {
return new HashSet<>(myWrap.getResourceNames());
}
@Override
public org.hl7.fhir.r4.model.StructureMap getTransform(String url) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getTypeNames() {
throw new UnsupportedOperationException();
}
@Override
public String getVersion() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasCache() {
return false;
}
@Override
public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> class_, String uri) {
throw new UnsupportedOperationException();
}
@Override
public boolean isNoTerminologyServer() {
return true;
}
@Override
public List<org.hl7.fhir.r4.model.StructureMap> listTransforms() {
throw new UnsupportedOperationException();
}
@Override
public IParser newJsonParser() {
throw new UnsupportedOperationException();
}
@Override
public IResourceValidator newValidator() throws FHIRException {
throw new UnsupportedOperationException();
}
@Override
public IParser newXmlParser() {
throw new UnsupportedOperationException();
}
@Override
public String oid2Uri(String code) {
throw new UnsupportedOperationException();
}
@Override
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsSystem(String system) throws TerminologyServiceException {
return myWrap.supportsSystem(system);
}
@Override
public TranslationServices translator() {
throw new UnsupportedOperationException();
}
@Override
public Set<String> typeTails() {
throw new UnsupportedOperationException();
}
@Override
public ValidationResult validateCode(String system, String code, String display) {
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(String system, String code, String display, org.hl7.fhir.r4.model.ValueSet vs) {
ValueSet convertedVs = null;
try {
if (vs != null) {
convertedVs = new VersionConvertor_10_40(myAdvisor).convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(org.hl7.fhir.r4.model.Coding code, org.hl7.fhir.r4.model.ValueSet vs) {
Coding convertedCode = null;
ValueSet convertedVs = null;
try {
if (code != null) {
convertedCode = new VersionConvertor_10_40(myAdvisor).convertCoding(code);
}
if (vs != null) {
convertedVs = new VersionConvertor_10_40(myAdvisor).convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(org.hl7.fhir.r4.model.CodeableConcept code, org.hl7.fhir.r4.model.ValueSet vs) {
CodeableConcept convertedCode = null;
ValueSet convertedVs = null;
try {
if (code != null) {
convertedCode = new VersionConvertor_10_40(myAdvisor).convertCodeableConcept(code);
}
if (vs != null) {
convertedVs = new VersionConvertor_10_40(myAdvisor).convertValueSet(vs);
}
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@Override
public ValidationResult validateCode(String system, String code, String display, org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent vsi) {
ValueSet.ConceptSetComponent conceptSetComponent = null;
if (vsi != null) {
try {
conceptSetComponent = new VersionConvertor_10_40(myAdvisor).convertConceptSetComponent(vsi);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
org.hl7.fhir.instance.utils.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display, conceptSetComponent);
return convertValidationResult(result);
}
}
private class NullAdvisor implements VersionConvertorAdvisor40 {
@Override
public Resource convertR2(org.hl7.fhir.r4.model.Resource resource) throws FHIRException {
return null;
}
@Override
public org.hl7.fhir.dstu3.model.Resource convertR3(org.hl7.fhir.r4.model.Resource resource) throws FHIRException {
return null;
}
@Override
public CodeSystem getCodeSystem(org.hl7.fhir.r4.model.ValueSet src) {
return null;
}
@Override
public void handleCodeSystem(CodeSystem tgtcs, org.hl7.fhir.r4.model.ValueSet source) {
// ignore
}
@Override
public boolean ignoreEntry(org.hl7.fhir.r4.model.Bundle.BundleEntryComponent src) {
return false;
}
}
private static class ResourceKey {
private final int myHashCode;
private String myResourceName;
private String myUri;
private ResourceKey(String theResourceName, String theUri) {
myResourceName = theResourceName;
myUri = theUri;
myHashCode = new HashCodeBuilder(17, 37)
.append(myResourceName)
.append(myUri)
.toHashCode();
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
ResourceKey that = (ResourceKey) theO;
return new EqualsBuilder()
.append(myResourceName, that.myResourceName)
.append(myUri, that.myUri)
.isEquals();
}
public String getResourceName() {
return myResourceName;
}
public String getUri() {
return myUri;
}
@Override
public int hashCode() {
return myHashCode;
}
}
}

View File

@ -33,7 +33,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public List<StructureDefinition> allStructures() {
throw new UnsupportedOperationException();
return myValidationSupport.allStructures();
}
@Override

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.instance.hapi.validation;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
@ -9,8 +10,15 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import ca.uhn.fhir.context.FhirContext;
import java.util.List;
public interface IValidationSupport {
/**
* Fetch all structuredefinitions
*/
List<StructureDefinition> allStructures();
/**
* Expands the given portion of a ValueSet
*

View File

@ -1,14 +1,14 @@
package org.hl7.fhir.instance.hapi.validation;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import java.util.ArrayList;
import java.util.List;
public class ValidationSupportChain implements IValidationSupport {
@ -37,6 +37,15 @@ public class ValidationSupportChain implements IValidationSupport {
myChain.add(theValidationSupport);
}
@Override
public List<StructureDefinition> allStructures() {
ArrayList<StructureDefinition> retVal = new ArrayList<>();
for (IValidationSupport next : myChain) {
retVal.addAll(next.allStructures());
}
return retVal;
}
@Override
public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) {
for (IValidationSupport next : myChain) {

View File

@ -2025,6 +2025,7 @@
<module>hapi-fhir-validation-resources-dstu3</module>
<module>hapi-fhir-structures-r4</module>
<module>hapi-fhir-validation-resources-r4</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-jaxrsserver-base</module>
<module>hapi-fhir-jaxrsserver-example</module>
<module>hapi-fhir-jpaserver-base</module>
@ -2048,7 +2049,6 @@
<module>example-projects/hapi-fhir-jpaserver-dynamic</module>
<module>tests/hapi-fhir-base-test-mindeps-client</module>
<module>tests/hapi-fhir-base-test-mindeps-server</module>
<module>hapi-fhir-igpacks</module>
<module>hapi-fhir-spring-boot</module>
<!--<module>hapi-fhir-osgi-core</module>-->
</modules>

View File

@ -45,6 +45,13 @@
This work was sponsored by the Regenstrief Institute. Thanks
to Regenstrief for their support!
</action>
<action type="add">
The DSTU2 validator has been refactored to use the same codebase
as the DSTU3/R4 validator (which were harmonized in HAPI FHIR 3.3.0).
This means that we now have a single codebase for all validators, which
improves maintainability and brings a number of improvements
to the accuracy of DSTU2 resource validation.
</action>
<action type="fix">
When encoding a resource that had contained resources with user-supplied
local IDs (e.g. resource.setId("#1")) as well as contained resources