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}