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