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}