001package org.hl7.fhir.dstu2016may.hapi.validation; 002 003import ca.uhn.fhir.context.FhirContext; 004import org.apache.commons.io.Charsets; 005import org.apache.commons.io.IOUtils; 006import org.apache.commons.lang3.Validate; 007import org.hl7.fhir.dstu2016may.model.*; 008import org.hl7.fhir.dstu2016may.model.Bundle.BundleEntryComponent; 009import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent; 010import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent; 011import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; 012import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; 013import org.hl7.fhir.instance.model.api.IBaseResource; 014import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 015 016import java.io.InputStream; 017import java.io.InputStreamReader; 018import java.util.*; 019 020import static org.apache.commons.lang3.StringUtils.isNotBlank; 021 022public class DefaultProfileValidationSupport implements IValidationSupport { 023 024 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class); 025 026 private Map<String, CodeSystem> myCodeSystems; 027 private Map<String, StructureDefinition> myStructureDefinitions; 028 private Map<String, ValueSet> myValueSets; 029 030 @Override 031 public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { 032 ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); 033 034 Set<String> wantCodes = new HashSet<String>(); 035 for (ConceptReferenceComponent next : theInclude.getConcept()) { 036 wantCodes.add(next.getCode()); 037 } 038 039 CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); 040 for (ConceptDefinitionComponent next : system.getConcept()) { 041 if (wantCodes.isEmpty() || wantCodes.contains(next.getCode())) { 042 retVal.addContains().setSystem(theInclude.getSystem()).setCode(next.getCode()).setDisplay(next.getDisplay()); 043 } 044 } 045 046 return retVal; 047 } 048 049 @Override 050 public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) { 051 ArrayList<IBaseResource> retVal = new ArrayList<>(); 052 retVal.addAll(myCodeSystems.values()); 053 retVal.addAll(myStructureDefinitions.values()); 054 retVal.addAll(myValueSets.values()); 055 return retVal; 056 } 057 058 @Override 059 public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) { 060 return new ArrayList<StructureDefinition>(provideStructureDefinitionMap(theContext).values()); 061 } 062 063 @Override 064 public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { 065 return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); 066 } 067 068 private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { 069 Map<String, CodeSystem> codeSystems = myCodeSystems; 070 Map<String, ValueSet> valueSets = myValueSets; 071 if (codeSystems == null) { 072 codeSystems = new HashMap<>(); 073 valueSets = new HashMap<>(); 074 075 loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu2016may/model/valueset/valuesets.xml"); 076 loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu2016may/model/valueset/v2-tables.xml"); 077 loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/dstu2016may/model/valueset/v3-codesystems.xml"); 078 079 myCodeSystems = codeSystems; 080 myValueSets = valueSets; 081 } 082 083 if (codeSystem) { 084 return codeSystems.get(theSystem); 085 } else { 086 return valueSets.get(theSystem); 087 } 088 } 089 090 @SuppressWarnings("unchecked") 091 @Override 092 public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) { 093 Validate.notBlank(theUri, "theUri must not be null or blank"); 094 095 if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) { 096 return (T) fetchStructureDefinition(theContext, theUri); 097 } 098 if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) { 099 return (T) fetchValueSet(theContext, theUri); 100 } 101 // if (theUri.startsWith("http://hl7.org/fhir/ValueSet/")) { 102 // Map<String, ValueSet> defaultValueSets = myDefaultValueSets; 103 // if (defaultValueSets == null) { 104 // String path = theContext.getVersion().getPathToSchemaDefinitions().replace("/schema", "/valueset") + "/valuesets.xml"; 105 // InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(path); 106 // if (valuesetText == null) { 107 // return null; 108 // } 109 // InputStreamReader reader; 110 // try { 111 // reader = new InputStreamReader(valuesetText, "UTF-8"); 112 // } catch (UnsupportedEncodingException e) { 113 // // Shouldn't happen! 114 // throw new InternalErrorException("UTF-8 encoding not supported on this platform", e); 115 // } 116 // 117 // defaultValueSets = new HashMap<String, ValueSet>(); 118 // 119 // Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); 120 // for (BundleEntryComponent next : bundle.getEntry()) { 121 // IdType nextId = new IdType(next.getFullUrl()); 122 // if (nextId.isEmpty() || !nextId.getValue().startsWith("http://hl7.org/fhir/ValueSet/")) { 123 // continue; 124 // } 125 // defaultValueSets.put(nextId.toVersionless().getValue(), (ValueSet) next.getResource()); 126 // } 127 // 128 // myDefaultValueSets = defaultValueSets; 129 // } 130 // 131 // return (T) defaultValueSets.get(theUri); 132 // } 133 134 return null; 135 } 136 137 @Override 138 public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { 139 return provideStructureDefinitionMap(theContext).get(theUrl); 140 } 141 142 @Override 143 public ValueSet fetchValueSet(FhirContext theContext, String uri) { 144 return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); 145 } 146 147 public void flush() { 148 myCodeSystems = null; 149 myStructureDefinitions = null; 150 } 151 152 @Override 153 public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { 154 CodeSystem cs = fetchCodeSystem(theContext, theSystem); 155 return cs != null; 156 } 157 158 private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) { 159 ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); 160 InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); 161 InputStreamReader reader = null; 162 if (inputStream != null) { 163 try { 164 reader = new InputStreamReader(inputStream, Charsets.UTF_8); 165 166 Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); 167 for (BundleEntryComponent next : bundle.getEntry()) { 168 if (next.getResource() instanceof CodeSystem) { 169 CodeSystem nextValueSet = (CodeSystem) next.getResource(); 170 nextValueSet.getText().setDivAsString(""); 171 String system = nextValueSet.getUrl(); 172 if (isNotBlank(system)) { 173 theCodeSystems.put(system, nextValueSet); 174 } 175 } else if (next.getResource() instanceof ValueSet) { 176 ValueSet nextValueSet = (ValueSet) next.getResource(); 177 nextValueSet.getText().setDivAsString(""); 178 String system = nextValueSet.getUrl(); 179 if (isNotBlank(system)) { 180 theValueSets.put(system, nextValueSet); 181 } 182 } 183 } 184 } finally { 185 IOUtils.closeQuietly(reader); 186 IOUtils.closeQuietly(inputStream); 187 } 188 } else { 189 ourLog.warn("Unable to load resource: {}", theClasspath); 190 } 191 } 192 193 private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) { 194 ourLog.info("Loading structure definitions from classpath: {}", theClasspath); 195 InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); 196 if (valuesetText != null) { 197 InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); 198 199 Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); 200 for (BundleEntryComponent next : bundle.getEntry()) { 201 if (next.getResource() instanceof StructureDefinition) { 202 StructureDefinition nextSd = (StructureDefinition) next.getResource(); 203 nextSd.getText().setDivAsString(""); 204 String system = nextSd.getUrl(); 205 if (isNotBlank(system)) { 206 theCodeSystems.put(system, nextSd); 207 } 208 } 209 } 210 } else { 211 ourLog.warn("Unable to load resource: {}", theClasspath); 212 } 213 } 214 215 private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) { 216 Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions; 217 if (structureDefinitions == null) { 218 structureDefinitions = new HashMap<>(); 219 220 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-resources.xml"); 221 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-types.xml"); 222 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-others.xml"); 223 224 myStructureDefinitions = structureDefinitions; 225 } 226 return structureDefinitions; 227 } 228 229 private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) { 230 String code = theCode; 231 if (theCaseSensitive == false) { 232 code = code.toUpperCase(); 233 } 234 235 return testIfConceptIsInListInner(conceptList, theCaseSensitive, code); 236 } 237 238 private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) { 239 CodeValidationResult retVal = null; 240 for (ConceptDefinitionComponent next : conceptList) { 241 String nextCandidate = next.getCode(); 242 if (theCaseSensitive == false) { 243 nextCandidate = nextCandidate.toUpperCase(); 244 } 245 if (nextCandidate.equals(code)) { 246 retVal = new CodeValidationResult(next); 247 break; 248 } 249 250 // recurse 251 retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive); 252 if (retVal != null) { 253 break; 254 } 255 } 256 257 return retVal; 258 } 259 260 @Override 261 public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { 262 CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); 263 if (cs != null) { 264 boolean caseSensitive = true; 265 if (cs.hasCaseSensitive()) { 266 caseSensitive = cs.getCaseSensitive(); 267 } 268 269 CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); 270 271 if (retVal != null) { 272 return retVal; 273 } 274 } 275 276 return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); 277 } 278 279}