001package ca.uhn.fhir.context.support;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2020 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
025import ca.uhn.fhir.util.ParametersUtil;
026import org.hl7.fhir.instance.model.api.IBase;
027import org.hl7.fhir.instance.model.api.IBaseParameters;
028import org.hl7.fhir.instance.model.api.IBaseResource;
029import org.hl7.fhir.instance.model.api.IPrimitiveType;
030
031import javax.annotation.Nonnull;
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.List;
035import java.util.Set;
036import java.util.stream.Collectors;
037
038import static org.apache.commons.lang3.StringUtils.isNotBlank;
039
040/**
041 * This interface is a version-independent representation of the
042 * various functions that can be provided by validation and terminology
043 * services.
044 * <p>
045 * Implementations are not required to implement all of the functions
046 * in this interface; in fact it is expected that most won't. Any
047 * methods which are not implemented may simply return <code>null</code>
048 * and calling code is expected to be able to handle this.
049 * </p>
050 */
051public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST> {
052
053        /**
054         * Expands the given portion of a ValueSet
055         *
056         * @param theInclude The portion to include
057         * @return The expansion
058         */
059        EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude);
060
061        /**
062         * Load and return all conformance resources associated with this
063         * validation support module. This method may return null if it doesn't
064         * make sense for a given module.
065         */
066        List<IBaseResource> fetchAllConformanceResources(FhirContext theContext);
067
068        /**
069         * Load and return all possible structure definitions
070         */
071        List<SDT> fetchAllStructureDefinitions(FhirContext theContext);
072
073        /**
074         * Fetch a code system by ID
075         *
076         * @param theSystem The code system
077         * @return The valueset (must not be null, but can be an empty ValueSet)
078         */
079        CST fetchCodeSystem(FhirContext theContext, String theSystem);
080
081        /**
082         * Loads a resource needed by the validation (a StructureDefinition, or a
083         * ValueSet)
084         *
085         * @param theContext The HAPI FHIR Context object current in use by the validator
086         * @param theClass   The type of the resource to load
087         * @param theUri     The resource URI
088         * @return Returns the resource, or <code>null</code> if no resource with the
089         * given URI can be found
090         */
091        <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri);
092
093        SDT fetchStructureDefinition(FhirContext theCtx, String theUrl);
094
095        /**
096         * Returns <code>true</code> if codes in the given code system can be expanded
097         * or validated
098         *
099         * @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code>
100         * @return Returns <code>true</code> if codes in the given code system can be
101         * validated
102         */
103        boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
104
105        /**
106         * Fetch the given ValueSet by URL
107         */
108        IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl);
109
110        /**
111         * Validates that the given code exists and if possible returns a display
112         * name. This method is called to check codes which are found in "example"
113         * binding fields (e.g. <code>Observation.code</code> in the default profile.
114         *
115         * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
116         * @param theCode       The code, e.g. "<code>1234-5</code>"
117         * @param theDisplay    The display name, if it should also be validated
118         * @return Returns a validation result object
119         */
120        CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
121
122        /**
123         * Validates that the given code exists and if possible returns a display
124         * name. This method is called to check codes which are found in "example"
125         * binding fields (e.g. <code>Observation.code</code> in the default profile.
126         *
127         * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
128         * @param theCode       The code, e.g. "<code>1234-5</code>"
129         * @param theDisplay    The display name, if it should also be validated
130         * @param theValueSet   The ValueSet to validate against. Must not be null, and must be a ValueSet resource.
131         * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request
132         */
133        default CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { return null; }
134
135        /**
136         * Look up a code using the system and code value
137         *
138         * @param theContext The FHIR context
139         * @param theSystem  The CodeSystem URL
140         * @param theCode    The code
141         */
142        LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode);
143
144        class ConceptDesignation {
145                private String myLanguage;
146                private String myUseSystem;
147                private String myUseCode;
148                private String myUseDisplay;
149                private String myValue;
150
151                public String getLanguage() {
152                        return myLanguage;
153                }
154
155                public ConceptDesignation setLanguage(String theLanguage) {
156                        myLanguage = theLanguage;
157                        return this;
158                }
159
160                public String getUseSystem() {
161                        return myUseSystem;
162                }
163
164                public ConceptDesignation setUseSystem(String theUseSystem) {
165                        myUseSystem = theUseSystem;
166                        return this;
167                }
168
169                public String getUseCode() {
170                        return myUseCode;
171                }
172
173                public ConceptDesignation setUseCode(String theUseCode) {
174                        myUseCode = theUseCode;
175                        return this;
176                }
177
178                public String getUseDisplay() {
179                        return myUseDisplay;
180                }
181
182                public ConceptDesignation setUseDisplay(String theUseDisplay) {
183                        myUseDisplay = theUseDisplay;
184                        return this;
185                }
186
187                public String getValue() {
188                        return myValue;
189                }
190
191                public ConceptDesignation setValue(String theValue) {
192                        myValue = theValue;
193                        return this;
194                }
195        }
196
197        abstract class BaseConceptProperty {
198                private final String myPropertyName;
199
200                /**
201                 * Constructor
202                 */
203                protected BaseConceptProperty(String thePropertyName) {
204                        myPropertyName = thePropertyName;
205                }
206
207                public String getPropertyName() {
208                        return myPropertyName;
209                }
210        }
211
212        class StringConceptProperty extends BaseConceptProperty {
213                private final String myValue;
214
215                /**
216                 * Constructor
217                 *
218                 * @param theName The name
219                 */
220                public StringConceptProperty(String theName, String theValue) {
221                        super(theName);
222                        myValue = theValue;
223                }
224
225                public String getValue() {
226                        return myValue;
227                }
228        }
229
230        class CodingConceptProperty extends BaseConceptProperty {
231                private final String myCode;
232                private final String myCodeSystem;
233                private final String myDisplay;
234
235                /**
236                 * Constructor
237                 *
238                 * @param theName The name
239                 */
240                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
241                        super(theName);
242                        myCodeSystem = theCodeSystem;
243                        myCode = theCode;
244                        myDisplay = theDisplay;
245                }
246
247                public String getCode() {
248                        return myCode;
249                }
250
251                public String getCodeSystem() {
252                        return myCodeSystem;
253                }
254
255                public String getDisplay() {
256                        return myDisplay;
257                }
258        }
259
260        class CodeValidationResult {
261                private IBase myDefinition;
262                private String myMessage;
263                private Enum mySeverity;
264                private String myCodeSystemName;
265                private String myCodeSystemVersion;
266                private List<BaseConceptProperty> myProperties;
267                private String myDisplay;
268
269                public CodeValidationResult(IBase theDefinition) {
270                        this.myDefinition = theDefinition;
271                }
272
273                public CodeValidationResult(Enum theSeverity, String message) {
274                        this.mySeverity = theSeverity;
275                        this.myMessage = message;
276                }
277
278                public CodeValidationResult(Enum theSeverity, String theMessage, IBase theDefinition, String theDisplay) {
279                        this.mySeverity = theSeverity;
280                        this.myMessage = theMessage;
281                        this.myDefinition = theDefinition;
282                        this.myDisplay = theDisplay;
283                }
284
285                public String getDisplay() {
286                        return myDisplay;
287                }
288
289                public IBase asConceptDefinition() {
290                        return myDefinition;
291                }
292
293                String getCodeSystemName() {
294                        return myCodeSystemName;
295                }
296
297                public void setCodeSystemName(String theCodeSystemName) {
298                        myCodeSystemName = theCodeSystemName;
299                }
300
301                public String getCodeSystemVersion() {
302                        return myCodeSystemVersion;
303                }
304
305                public void setCodeSystemVersion(String theCodeSystemVersion) {
306                        myCodeSystemVersion = theCodeSystemVersion;
307                }
308
309                public String getMessage() {
310                        return myMessage;
311                }
312
313                public List<BaseConceptProperty> getProperties() {
314                        return myProperties;
315                }
316
317                public void setProperties(List<BaseConceptProperty> theProperties) {
318                        myProperties = theProperties;
319                }
320
321                public Enum getSeverity() {
322                        return mySeverity;
323                }
324
325                public boolean isOk() {
326                        return myDefinition != null;
327                }
328
329                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
330                        LookupCodeResult retVal = new LookupCodeResult();
331                        retVal.setSearchedForSystem(theSearchedForSystem);
332                        retVal.setSearchedForCode(theSearchedForCode);
333                        if (isOk()) {
334                                retVal.setFound(true);
335                                retVal.setCodeDisplay(myDisplay);
336                                retVal.setCodeSystemDisplayName(getCodeSystemName());
337                                retVal.setCodeSystemVersion(getCodeSystemVersion());
338                        }
339                        return retVal;
340                }
341
342        }
343
344        class LookupCodeResult {
345
346                private String myCodeDisplay;
347                private boolean myCodeIsAbstract;
348                private String myCodeSystemDisplayName;
349                private String myCodeSystemVersion;
350                private boolean myFound;
351                private String mySearchedForCode;
352                private String mySearchedForSystem;
353                private List<IContextValidationSupport.BaseConceptProperty> myProperties;
354                private List<ConceptDesignation> myDesignations;
355
356                /**
357                 * Constructor
358                 */
359                public LookupCodeResult() {
360                        super();
361                }
362
363                public List<BaseConceptProperty> getProperties() {
364                        if (myProperties == null) {
365                                myProperties = new ArrayList<>();
366                        }
367                        return myProperties;
368                }
369
370                public void setProperties(List<IContextValidationSupport.BaseConceptProperty> theProperties) {
371                        myProperties = theProperties;
372                }
373
374                @Nonnull
375                public List<ConceptDesignation> getDesignations() {
376                        if (myDesignations == null) {
377                                myDesignations = new ArrayList<>();
378                        }
379                        return myDesignations;
380                }
381
382                public String getCodeDisplay() {
383                        return myCodeDisplay;
384                }
385
386                public void setCodeDisplay(String theCodeDisplay) {
387                        myCodeDisplay = theCodeDisplay;
388                }
389
390                public String getCodeSystemDisplayName() {
391                        return myCodeSystemDisplayName;
392                }
393
394                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
395                        myCodeSystemDisplayName = theCodeSystemDisplayName;
396                }
397
398                public String getCodeSystemVersion() {
399                        return myCodeSystemVersion;
400                }
401
402                public void setCodeSystemVersion(String theCodeSystemVersion) {
403                        myCodeSystemVersion = theCodeSystemVersion;
404                }
405
406                public String getSearchedForCode() {
407                        return mySearchedForCode;
408                }
409
410                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
411                        mySearchedForCode = theSearchedForCode;
412                        return this;
413                }
414
415                public String getSearchedForSystem() {
416                        return mySearchedForSystem;
417                }
418
419                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
420                        mySearchedForSystem = theSearchedForSystem;
421                        return this;
422                }
423
424                public boolean isCodeIsAbstract() {
425                        return myCodeIsAbstract;
426                }
427
428                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
429                        myCodeIsAbstract = theCodeIsAbstract;
430                }
431
432                public boolean isFound() {
433                        return myFound;
434                }
435
436                public LookupCodeResult setFound(boolean theFound) {
437                        myFound = theFound;
438                        return this;
439                }
440
441                public void throwNotFoundIfAppropriate() {
442                        if (isFound() == false) {
443                                throw new ResourceNotFoundException("Unable to find code[" + getSearchedForCode() + "] in system[" + getSearchedForSystem() + "]");
444                        }
445                }
446
447                public IBaseParameters toParameters(FhirContext theContext, List<? extends IPrimitiveType<String>> theProperties) {
448
449                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
450                        if (isNotBlank(getCodeSystemDisplayName())) {
451                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
452                        }
453                        if (isNotBlank(getCodeSystemVersion())) {
454                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
455                        }
456                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
457                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
458
459                        if (myProperties != null) {
460
461                                Set<String> properties = Collections.emptySet();
462                                if (theProperties != null) {
463                                        properties = theProperties
464                                                .stream()
465                                                .map(IPrimitiveType::getValueAsString)
466                                                .collect(Collectors.toSet());
467                                }
468
469                                for (IContextValidationSupport.BaseConceptProperty next : myProperties) {
470
471                                        if (!properties.isEmpty()) {
472                                                if (!properties.contains(next.getPropertyName())) {
473                                                        continue;
474                                                }
475                                        }
476
477                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
478                                        ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName());
479
480                                        if (next instanceof IContextValidationSupport.StringConceptProperty) {
481                                                IContextValidationSupport.StringConceptProperty prop = (IContextValidationSupport.StringConceptProperty) next;
482                                                ParametersUtil.addPartString(theContext, property, "value", prop.getValue());
483                                        } else if (next instanceof IContextValidationSupport.CodingConceptProperty) {
484                                                IContextValidationSupport.CodingConceptProperty prop = (IContextValidationSupport.CodingConceptProperty) next;
485                                                ParametersUtil.addPartCoding(theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay());
486                                        } else {
487                                                throw new IllegalStateException("Don't know how to handle " + next.getClass());
488                                        }
489                                }
490                        }
491
492                        if (myDesignations != null) {
493                                for (ConceptDesignation next : myDesignations) {
494
495                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
496                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
497                                        ParametersUtil.addPartCoding(theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
498                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
499                                }
500                        }
501
502                        return retVal;
503                }
504
505                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
506                        return new LookupCodeResult()
507                                .setFound(false)
508                                .setSearchedForSystem(theSearchedForSystem)
509                                .setSearchedForCode(theSearchedForCode);
510                }
511        }
512
513}