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}