package uk.org.hl7.fhir.validation.stu3; import ca.uhn.fhir.context.FhirContext; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import uk.org.hl7.fhir.core.Stu3.CareConnectSystem; import java.io.InputStream; import java.io.InputStreamReader; import java.util.*; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNumeric; public class SNOMEDUKMockValidationSupport implements IValidationSupport { private static final String URL_PREFIX_VALUE_SET = "https://fhir.hl7.org.uk/STU3/ValueSet/"; private static final String URL_PREFIX_STRUCTURE_DEFINITION = "https://fhir.hl7.org.uk/STU3/StructureDefinition/"; private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "https://fhir.hl7.org.uk/STU3/"; private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SNOMEDUKMockValidationSupport.class); private Map<String, CodeSystem> myCodeSystems; private Map<String, StructureDefinition> myStructureDefinitions; private Map<String, ValueSet> myValueSets; private void logD(String message) { log.debug(message); // System.out.println(message); } private void logW(String message) { log.warn(message); System.out.println(message); } @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { // logD("SNOMED MOCK expandValueSet System="+theInclude.getSystem()); ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); Set<String> wantCodes = new HashSet<String>(); for (ConceptReferenceComponent next : theInclude.getConcept()) { logD("SNOMED MOCK expandValueSet System="+theInclude.getSystem()+" wantCodes.add "+next.getCode()); wantCodes.add(next.getCode()); } CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); for (ConceptDefinitionComponent next : system.getConcept()) { if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) { retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); } } return retVal; } @Override public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) { ArrayList<IBaseResource> retVal = new ArrayList<>(); retVal.addAll(myCodeSystems.values()); retVal.addAll(myStructureDefinitions.values()); retVal.addAll(myValueSets.values()); return retVal; } @Override public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) { return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values()); } @Override public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { //logD("SNOMEDMOCK fetchCodeSystem "+theSystem); return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); } private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { synchronized (this) { logD("SNOMEDMOCK fetchCodeSystemOrValueSet: system="+theSystem); Map<String, CodeSystem> codeSystems = myCodeSystems; Map<String, ValueSet> valueSets = myValueSets; if (codeSystems == null || valueSets == null) { codeSystems = new HashMap<String, CodeSystem>(); valueSets = new HashMap<String, ValueSet>(); if (theSystem.equals(CareConnectSystem.SNOMEDCT)) { // Mock SNOMED support TODO point to real SNOMED UK Server CodeSystem SNOMEDSystem = new CodeSystem(); SNOMEDSystem.setStatus(Enumerations.PublicationStatus.ACTIVE); SNOMEDSystem.setUrl(CareConnectSystem.SNOMEDCT); codeSystems.put(CareConnectSystem.SNOMEDCT,SNOMEDSystem); } myCodeSystems = codeSystems; myValueSets = valueSets; } if (codeSystem) { return codeSystems.get(theSystem); } else { return valueSets.get(theSystem); } } } @SuppressWarnings("unchecked") @Override public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) { Validate.notBlank(theUri, "theUri must not be null or blank"); if (theClass.equals(StructureDefinition.class)) { return (T) fetchStructureDefinition(theContext, theUri); } if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { return (T) fetchValueSet(theContext, theUri); } return null; } @Override public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { String url = theUrl; if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { // no change } else if (url.indexOf('/') == -1) { url = URL_PREFIX_STRUCTURE_DEFINITION + url; } else if (StringUtils.countMatches(url, '/') == 1) { url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; } return provideStructureDefinitionMap(theContext).get(url); } public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); } public void flush() { myCodeSystems = null; myStructureDefinitions = null; } @Override public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { CodeSystem cs = fetchCodeSystem(theContext, theSystem); return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; } private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) { logD("SNOMEDMOCK Loading CodeSystem/ValueSet from classpath: "+ theClasspath); InputStream valuesetText = SNOMEDUKMockValidationSupport.class.getResourceAsStream(theClasspath); if (valuesetText != null) { InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); for (BundleEntryComponent next : bundle.getEntry()) { if (next.getResource() instanceof CodeSystem) { CodeSystem nextValueSet = (CodeSystem) next.getResource(); nextValueSet.getText().setDivAsString(""); String system = nextValueSet.getUrl(); if (isNotBlank(system)) { theCodeSystems.put(system, nextValueSet); } } else if (next.getResource() instanceof ValueSet) { ValueSet nextValueSet = (ValueSet) next.getResource(); nextValueSet.getText().setDivAsString(""); String system = nextValueSet.getUrl(); if (isNotBlank(system)) { theValueSets.put(system, nextValueSet); } } } } else { logW("Unable to load resource: "+ theClasspath); } } private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) { logD("SNOMEDMOCK Loading structure definitions from classpath: "+ theClasspath); InputStream valuesetText = SNOMEDUKMockValidationSupport.class.getResourceAsStream(theClasspath); if (valuesetText != null) { InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); for (BundleEntryComponent next : bundle.getEntry()) { if (next.getResource() instanceof StructureDefinition) { StructureDefinition nextSd = (StructureDefinition) next.getResource(); nextSd.getText().setDivAsString(""); String system = nextSd.getUrl(); if (isNotBlank(system)) { theCodeSystems.put(system, nextSd); } } } } else { log.warn("Unable to load resource: {}", theClasspath); } } private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) { Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions; if (structureDefinitions == null) { structureDefinitions = new HashMap<String, StructureDefinition>(); myStructureDefinitions = structureDefinitions; } return structureDefinitions; } private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) { logD("SNOMEDMOCK testIfConceptIsInList: {} code="+ theCode); String code = theCode; if (theCaseSensitive == false) { code = code.toUpperCase(); } return testIfConceptIsInListInner(conceptList, theCaseSensitive, code); } private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) { logD("SNOMEDMOCK testIfConceptIsInListInner: code=" + code); /* This is a mock and we will do a basic check (is the code Numeric! return positive if numeric else false */ CodeValidationResult retVal = null; if (isNumeric(code)) { ConceptDefinitionComponent concept = new ConceptDefinitionComponent(); concept.setCode(code); retVal = new CodeValidationResult(concept); } /* Ignore the list for now KGM Dec 2017 TODO for (ConceptDefinitionComponent next : conceptList) { // KGM logD("SNOMEDMOCK testIfConceptIsInListInner NextCode = "+next.getCode()); String nextCandidate = next.getCode(); } */ return retVal; } @Override public StructureDefinition generateSnapshot(StructureDefinition structureDefinition, String s, String s1) { return null; } @Override public LookupCodeResult lookupCode(FhirContext fhirContext, String s, String s1) { return null; } @Override public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); logD("SNOMEDMOCK validateCode system = "+ theCodeSystem); if (cs != null) { boolean caseSensitive = true; if (cs.hasCaseSensitive()) { caseSensitive = cs.getCaseSensitive(); } CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); if (retVal != null) { return retVal; } } return new CodeValidationResult(IssueSeverity.WARNING, "SNOMEDMOCK Unknown code: " + theCodeSystem + " / " + theCode); } }