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