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 ValueSet fetchValueSet(FhirContext theContext, String theSystem) { 143 return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); 144 } 145 146 public void flush() { 147 myCodeSystems = null; 148 myStructureDefinitions = null; 149 } 150 151 @Override 152 public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { 153 CodeSystem cs = fetchCodeSystem(theContext, theSystem); 154 return cs != null; 155 } 156 157 private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) { 158 ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); 159 InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); 160 InputStreamReader reader = null; 161 if (inputStream != null) { 162 try { 163 reader = new InputStreamReader(inputStream, Charsets.UTF_8); 164 165 Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); 166 for (BundleEntryComponent next : bundle.getEntry()) { 167 if (next.getResource() instanceof CodeSystem) { 168 CodeSystem nextValueSet = (CodeSystem) next.getResource(); 169 nextValueSet.getText().setDivAsString(""); 170 String system = nextValueSet.getUrl(); 171 if (isNotBlank(system)) { 172 theCodeSystems.put(system, nextValueSet); 173 } 174 } else if (next.getResource() instanceof ValueSet) { 175 ValueSet nextValueSet = (ValueSet) next.getResource(); 176 nextValueSet.getText().setDivAsString(""); 177 String system = nextValueSet.getUrl(); 178 if (isNotBlank(system)) { 179 theValueSets.put(system, nextValueSet); 180 } 181 } 182 } 183 } finally { 184 IOUtils.closeQuietly(reader); 185 IOUtils.closeQuietly(inputStream); 186 } 187 } else { 188 ourLog.warn("Unable to load resource: {}", theClasspath); 189 } 190 } 191 192 private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) { 193 ourLog.info("Loading structure definitions from classpath: {}", theClasspath); 194 InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); 195 if (valuesetText != null) { 196 InputStreamReader reader = new InputStreamReader(valuesetText, Charsets.UTF_8); 197 198 Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); 199 for (BundleEntryComponent next : bundle.getEntry()) { 200 if (next.getResource() instanceof StructureDefinition) { 201 StructureDefinition nextSd = (StructureDefinition) next.getResource(); 202 nextSd.getText().setDivAsString(""); 203 String system = nextSd.getUrl(); 204 if (isNotBlank(system)) { 205 theCodeSystems.put(system, nextSd); 206 } 207 } 208 } 209 } else { 210 ourLog.warn("Unable to load resource: {}", theClasspath); 211 } 212 } 213 214 private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) { 215 Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions; 216 if (structureDefinitions == null) { 217 structureDefinitions = new HashMap<String, StructureDefinition>(); 218 219 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-resources.xml"); 220 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-types.xml"); 221 loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/dstu2016may/model/profile/profiles-others.xml"); 222 223 myStructureDefinitions = structureDefinitions; 224 } 225 return structureDefinitions; 226 } 227 228 private CodeValidationResult testIfConceptIsInList(String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) { 229 String code = theCode; 230 if (theCaseSensitive == false) { 231 code = code.toUpperCase(); 232 } 233 234 return testIfConceptIsInListInner(conceptList, theCaseSensitive, code); 235 } 236 237 private CodeValidationResult testIfConceptIsInListInner(List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) { 238 CodeValidationResult retVal = null; 239 for (ConceptDefinitionComponent next : conceptList) { 240 String nextCandidate = next.getCode(); 241 if (theCaseSensitive == false) { 242 nextCandidate = nextCandidate.toUpperCase(); 243 } 244 if (nextCandidate.equals(code)) { 245 retVal = new CodeValidationResult(next); 246 break; 247 } 248 249 // recurse 250 retVal = testIfConceptIsInList(code, next.getConcept(), theCaseSensitive); 251 if (retVal != null) { 252 break; 253 } 254 } 255 256 return retVal; 257 } 258 259 @Override 260 public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { 261 CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); 262 if (cs != null) { 263 boolean caseSensitive = true; 264 if (cs.hasCaseSensitive()) { 265 caseSensitive = cs.getCaseSensitive(); 266 } 267 268 CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive); 269 270 if (retVal != null) { 271 return retVal; 272 } 273 } 274 275 return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode); 276 } 277 278}