001package org.hl7.fhir.dstu2016may.hapi.validation;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004import static org.apache.commons.lang3.StringUtils.isNotBlank;
005
006import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collections;
010import java.util.HashMap;
011import java.util.HashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015
016import org.apache.commons.lang3.Validate;
017import org.hl7.fhir.dstu2016may.formats.IParser;
018import org.hl7.fhir.dstu2016may.formats.ParserType;
019import org.hl7.fhir.dstu2016may.hapi.validation.IValidationSupport.CodeValidationResult;
020import org.hl7.fhir.dstu2016may.model.CodeSystem;
021import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent;
022import org.hl7.fhir.dstu2016may.model.CodeType;
023import org.hl7.fhir.dstu2016may.model.CodeableConcept;
024import org.hl7.fhir.dstu2016may.model.Coding;
025import org.hl7.fhir.dstu2016may.model.ConceptMap;
026import org.hl7.fhir.dstu2016may.model.Resource;
027import org.hl7.fhir.dstu2016may.model.ResourceType;
028import org.hl7.fhir.dstu2016may.model.StructureDefinition;
029import org.hl7.fhir.dstu2016may.model.ValueSet;
030import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent;
031import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
032import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent;
033import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionContainsComponent;
034import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander;
035import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
036import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderFactory;
037import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderSimple;
038import org.hl7.fhir.dstu2016may.utils.IWorkerContext;
039import org.hl7.fhir.dstu2016may.validation.IResourceValidator;
040
041import ca.uhn.fhir.context.FhirContext;
042import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
043import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
044
045public final class HapiWorkerContext implements IWorkerContext, ValueSetExpanderFactory  {
046        private final FhirContext myCtx;
047        private Map<String, Resource> myFetchedResourceCache = new HashMap<String, Resource>();
048        private IValidationSupport myValidationSupport;
049
050        public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) {
051                Validate.notNull(theCtx, "theCtx must not be null");
052                Validate.notNull(theValidationSupport, "theValidationSupport must not be null");
053                myCtx = theCtx;
054                myValidationSupport = theValidationSupport;
055        }
056
057        @Override
058        public List<StructureDefinition> allStructures() {
059                return myValidationSupport.fetchAllStructureDefinitions(myCtx);
060        }
061
062        @Override
063        public CodeSystem fetchCodeSystem(String theSystem) {
064                if (myValidationSupport == null) {
065                        return null;
066                } else {
067                        return myValidationSupport.fetchCodeSystem(myCtx, theSystem);
068                }
069        }
070
071        @Override
072        public <T extends Resource> T fetchResource(Class<T> theClass, String theUri) {
073                if (myValidationSupport == null) {
074                        return null;
075                } else {
076                        @SuppressWarnings("unchecked")
077                        T retVal = (T) myFetchedResourceCache.get(theUri);
078                        if (retVal == null) {
079                                retVal = myValidationSupport.fetchResource(myCtx, theClass, theUri);
080                                if (retVal != null) {
081                                        myFetchedResourceCache.put(theUri, retVal);
082                                }
083                        }
084                        return retVal;
085                }
086        }
087
088        @Override
089        public List<ConceptMap> findMapsForSource(String theUrl) {
090                throw new UnsupportedOperationException();
091        }
092
093        @Override
094        public String getAbbreviation(String theName) {
095                throw new UnsupportedOperationException();
096        }
097
098
099        @Override
100        public IParser getParser(ParserType theType) {
101                throw new UnsupportedOperationException();
102        }
103
104        @Override
105        public IParser getParser(String theType) {
106                throw new UnsupportedOperationException();
107        }
108
109        @Override
110        public List<String> getResourceNames() {
111                List<String> result = new ArrayList<String>();
112                for (ResourceType next : ResourceType.values()) {
113                        result.add(next.name());
114                }
115                Collections.sort(result);
116                return result;
117        }
118
119        @Override
120        public <T extends Resource> boolean hasResource(Class<T> theClass_, String theUri) {
121                throw new UnsupportedOperationException();
122        }
123
124        @Override
125        public IParser newJsonParser() {
126                throw new UnsupportedOperationException();
127        }
128
129        @Override
130        public IResourceValidator newValidator() {
131                throw new UnsupportedOperationException();
132        }
133
134        @Override
135        public IParser newXmlParser() {
136                throw new UnsupportedOperationException();
137        }
138
139        @Override
140        public String oid2Uri(String theCode) {
141                throw new UnsupportedOperationException();
142        }
143
144        @Override
145        public boolean supportsSystem(String theSystem) {
146                if (myValidationSupport == null) {
147                        return false;
148                } else {
149                        return myValidationSupport.isCodeSystemSupported(myCtx, theSystem);
150                }
151        }
152
153        @Override
154        public Set<String> typeTails() {
155                return new HashSet<String>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code",
156                                "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint",
157                                "Timing", "Reference", "Annotation", "Signature", "Meta"));
158        }
159
160        @Override
161        public ValidationResult validateCode(CodeableConcept theCode, ValueSet theVs) {
162                for (Coding next : theCode.getCoding()) {
163                        ValidationResult retVal = validateCode(next, theVs);
164                        if (retVal != null && retVal.isOk()) {
165                                return retVal;
166                        }
167                }
168
169                return new ValidationResult(null, null);
170        }
171
172        @Override
173        public ValidationResult validateCode(Coding theCode, ValueSet theVs) {
174                String system = theCode.getSystem();
175                String code = theCode.getCode();
176                String display = theCode.getDisplay();
177                return validateCode(system, code, display, theVs);
178        }
179
180        @Override
181        public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) {
182                CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay);
183                if (result == null) {
184                        return null;
185                }
186                return new ValidationResult(result.getSeverity(), result.getMessage(), result.asConceptDefinition());
187        }
188
189        @Override
190        public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) {
191                throw new UnsupportedOperationException();
192        }
193
194        @Override
195        public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ValueSet theVs) {
196
197                if (theVs != null && isNotBlank(theCode)) {
198                        for (ConceptSetComponent next : theVs.getCompose().getInclude()) {
199                                if (isBlank(theSystem) || theSystem.equals(next.getSystem())) {
200                                        for (ConceptReferenceComponent nextCode : next.getConcept()) {
201                                                if (theCode.equals(nextCode.getCode())) {
202                                                        CodeType code = new CodeType(theCode);
203                                                        return new ValidationResult(new ConceptDefinitionComponent(code));
204                                                }
205                                        }
206                                }
207                        }
208                }
209
210
211                boolean caseSensitive = true;
212                if (isNotBlank(theSystem)) {
213                        CodeSystem system = fetchCodeSystem(theSystem);
214                        if (system == null) {
215                                return new ValidationResult(IssueSeverity.INFORMATION, "Code " + theSystem + "/" + theCode + " was not validated because the code system is not present");
216                        }
217
218                        if (system.hasCaseSensitive()) {
219                                caseSensitive = system.getCaseSensitive();
220                        }
221                }
222
223                String wantCode = theCode;
224                if (!caseSensitive) {
225                        wantCode = wantCode.toUpperCase();
226                }
227
228                ValueSetExpansionOutcome expandedValueSet = null;
229
230                /*
231                 * The following valueset is a special case, since the BCP codesystem is very difficult to expand
232                 */
233                if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) {
234                        ValueSet expansion = new ValueSet();
235                        for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
236                                for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {
237                                        expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay());
238                                }
239                        }
240                        expandedValueSet = new ValueSetExpansionOutcome(expansion);
241                }
242
243                if (expandedValueSet == null) {
244                        expandedValueSet = expandVS(theVs, true);
245                }
246
247                for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
248                        String nextCode = next.getCode();
249                        if (!caseSensitive) {
250                                nextCode = nextCode.toUpperCase();
251                        }
252
253                        if (nextCode.equals(wantCode)) {
254                                if (theSystem == null || next.getSystem().equals(theSystem)) {
255                                        ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
256                                        definition.setCode(next.getCode());
257                                        definition.setDisplay(next.getDisplay());
258                                        ValidationResult retVal = new ValidationResult(definition);
259                                        return retVal;
260                                }
261                        }
262                }
263
264                return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
265        }
266
267        @Override
268        public ValueSetExpander getExpander() {
269                ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this, this);
270                return retVal;
271        }
272
273        @Override
274        public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk) {
275                ValueSetExpansionOutcome vso;
276                try {
277                        vso = getExpander().expand(theSource);
278                } catch (InvalidRequestException e) {
279                        throw e;
280                } catch (Exception e) {
281                        throw new InternalErrorException(e);
282                }
283                if (vso.getError() != null) {
284                        throw new InvalidRequestException(vso.getError());
285                } else {
286                        return vso;
287                }
288        }
289
290        @Override
291        public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc) {
292                throw new UnsupportedOperationException();
293        }
294
295
296}