001package ca.uhn.fhir.context.support;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2019 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         * Validates that the given code exists and if possible returns a display
107         * name. This method is called to check codes which are found in "example"
108         * binding fields (e.g. <code>Observation.code</code> in the default profile.
109         *
110         * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
111         * @param theCode       The code, e.g. "<code>1234-5</code>"
112         * @param theDisplay    The display name, if it should also be validated
113         * @return Returns a validation result object
114         */
115        CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
116
117        /**
118         * Look up a code using the system and code value
119         *
120         * @param theContext The FHIR context
121         * @param theSystem  The CodeSystem URL
122         * @param theCode    The code
123         */
124        LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode);
125
126        class ConceptDesignation {
127                private String myLanguage;
128                private String myUseSystem;
129                private String myUseCode;
130                private String myUseDisplay;
131                private String myValue;
132
133                public String getLanguage() {
134                        return myLanguage;
135                }
136
137                public ConceptDesignation setLanguage(String theLanguage) {
138                        myLanguage = theLanguage;
139                        return this;
140                }
141
142                public String getUseSystem() {
143                        return myUseSystem;
144                }
145
146                public ConceptDesignation setUseSystem(String theUseSystem) {
147                        myUseSystem = theUseSystem;
148                        return this;
149                }
150
151                public String getUseCode() {
152                        return myUseCode;
153                }
154
155                public ConceptDesignation setUseCode(String theUseCode) {
156                        myUseCode = theUseCode;
157                        return this;
158                }
159
160                public String getUseDisplay() {
161                        return myUseDisplay;
162                }
163
164                public ConceptDesignation setUseDisplay(String theUseDisplay) {
165                        myUseDisplay = theUseDisplay;
166                        return this;
167                }
168
169                public String getValue() {
170                        return myValue;
171                }
172
173                public ConceptDesignation setValue(String theValue) {
174                        myValue = theValue;
175                        return this;
176                }
177        }
178
179        abstract class BaseConceptProperty {
180                private final String myPropertyName;
181
182                /**
183                 * Constructor
184                 */
185                protected BaseConceptProperty(String thePropertyName) {
186                        myPropertyName = thePropertyName;
187                }
188
189                public String getPropertyName() {
190                        return myPropertyName;
191                }
192        }
193
194        class StringConceptProperty extends BaseConceptProperty {
195                private final String myValue;
196
197                /**
198                 * Constructor
199                 *
200                 * @param theName The name
201                 */
202                public StringConceptProperty(String theName, String theValue) {
203                        super(theName);
204                        myValue = theValue;
205                }
206
207                public String getValue() {
208                        return myValue;
209                }
210        }
211
212        class CodingConceptProperty extends BaseConceptProperty {
213                private final String myCode;
214                private final String myCodeSystem;
215                private final String myDisplay;
216
217                /**
218                 * Constructor
219                 *
220                 * @param theName The name
221                 */
222                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
223                        super(theName);
224                        myCodeSystem = theCodeSystem;
225                        myCode = theCode;
226                        myDisplay = theDisplay;
227                }
228
229                public String getCode() {
230                        return myCode;
231                }
232
233                public String getCodeSystem() {
234                        return myCodeSystem;
235                }
236
237                public String getDisplay() {
238                        return myDisplay;
239                }
240        }
241
242        abstract class CodeValidationResult<CDCT, IST> {
243                private CDCT myDefinition;
244                private String myMessage;
245                private IST mySeverity;
246                private String myCodeSystemName;
247                private String myCodeSystemVersion;
248                private List<BaseConceptProperty> myProperties;
249
250                public CodeValidationResult(CDCT theNext) {
251                        this.myDefinition = theNext;
252                }
253
254                public CodeValidationResult(IST severity, String message) {
255                        this.mySeverity = severity;
256                        this.myMessage = message;
257                }
258
259                public CodeValidationResult(IST severity, String message, CDCT definition) {
260                        this.mySeverity = severity;
261                        this.myMessage = message;
262                        this.myDefinition = definition;
263                }
264
265                public CDCT asConceptDefinition() {
266                        return myDefinition;
267                }
268
269                public String getCodeSystemName() {
270                        return myCodeSystemName;
271                }
272
273                public void setCodeSystemName(String theCodeSystemName) {
274                        myCodeSystemName = theCodeSystemName;
275                }
276
277                public String getCodeSystemVersion() {
278                        return myCodeSystemVersion;
279                }
280
281                public void setCodeSystemVersion(String theCodeSystemVersion) {
282                        myCodeSystemVersion = theCodeSystemVersion;
283                }
284
285                public String getMessage() {
286                        return myMessage;
287                }
288
289                public List<BaseConceptProperty> getProperties() {
290                        return myProperties;
291                }
292
293                public void setProperties(List<BaseConceptProperty> theProperties) {
294                        myProperties = theProperties;
295                }
296
297                public IST getSeverity() {
298                        return mySeverity;
299                }
300
301                public boolean isOk() {
302                        return myDefinition != null;
303                }
304
305                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
306                        LookupCodeResult retVal = new LookupCodeResult();
307                        retVal.setSearchedForSystem(theSearchedForSystem);
308                        retVal.setSearchedForCode(theSearchedForCode);
309                        if (isOk()) {
310                                retVal.setFound(true);
311                                retVal.setCodeDisplay(getDisplay());
312                                retVal.setCodeSystemDisplayName(getCodeSystemName());
313                                retVal.setCodeSystemVersion(getCodeSystemVersion());
314                        }
315                        return retVal;
316                }
317
318                protected abstract String getDisplay();
319
320        }
321
322        class LookupCodeResult {
323
324                private String myCodeDisplay;
325                private boolean myCodeIsAbstract;
326                private String myCodeSystemDisplayName;
327                private String myCodeSystemVersion;
328                private boolean myFound;
329                private String mySearchedForCode;
330                private String mySearchedForSystem;
331                private List<IContextValidationSupport.BaseConceptProperty> myProperties;
332                private List<ConceptDesignation> myDesignations;
333
334                /**
335                 * Constructor
336                 */
337                public LookupCodeResult() {
338                        super();
339                }
340
341                public List<BaseConceptProperty> getProperties() {
342                        if (myProperties == null) {
343                                myProperties = new ArrayList<>();
344                        }
345                        return myProperties;
346                }
347
348                public void setProperties(List<IContextValidationSupport.BaseConceptProperty> theProperties) {
349                        myProperties = theProperties;
350                }
351
352                @Nonnull
353                public List<ConceptDesignation> getDesignations() {
354                        if (myDesignations == null) {
355                                myDesignations = new ArrayList<>();
356                        }
357                        return myDesignations;
358                }
359
360                public String getCodeDisplay() {
361                        return myCodeDisplay;
362                }
363
364                public void setCodeDisplay(String theCodeDisplay) {
365                        myCodeDisplay = theCodeDisplay;
366                }
367
368                public String getCodeSystemDisplayName() {
369                        return myCodeSystemDisplayName;
370                }
371
372                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
373                        myCodeSystemDisplayName = theCodeSystemDisplayName;
374                }
375
376                public String getCodeSystemVersion() {
377                        return myCodeSystemVersion;
378                }
379
380                public void setCodeSystemVersion(String theCodeSystemVersion) {
381                        myCodeSystemVersion = theCodeSystemVersion;
382                }
383
384                public String getSearchedForCode() {
385                        return mySearchedForCode;
386                }
387
388                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
389                        mySearchedForCode = theSearchedForCode;
390                        return this;
391                }
392
393                public String getSearchedForSystem() {
394                        return mySearchedForSystem;
395                }
396
397                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
398                        mySearchedForSystem = theSearchedForSystem;
399                        return this;
400                }
401
402                public boolean isCodeIsAbstract() {
403                        return myCodeIsAbstract;
404                }
405
406                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
407                        myCodeIsAbstract = theCodeIsAbstract;
408                }
409
410                public boolean isFound() {
411                        return myFound;
412                }
413
414                public LookupCodeResult setFound(boolean theFound) {
415                        myFound = theFound;
416                        return this;
417                }
418
419                public void throwNotFoundIfAppropriate() {
420                        if (isFound() == false) {
421                                throw new ResourceNotFoundException("Unable to find code[" + getSearchedForCode() + "] in system[" + getSearchedForSystem() + "]");
422                        }
423                }
424
425                public IBaseParameters toParameters(FhirContext theContext, List<? extends IPrimitiveType<String>> theProperties) {
426
427                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
428                        if (isNotBlank(getCodeSystemDisplayName())) {
429                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
430                        }
431                        if (isNotBlank(getCodeSystemVersion())) {
432                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
433                        }
434                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
435                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
436
437                        if (myProperties != null) {
438
439                                Set<String> properties = Collections.emptySet();
440                                if (theProperties != null) {
441                                        properties = theProperties
442                                                .stream()
443                                                .map(IPrimitiveType::getValueAsString)
444                                                .collect(Collectors.toSet());
445                                }
446
447                                for (IContextValidationSupport.BaseConceptProperty next : myProperties) {
448
449                                        if (!properties.isEmpty()) {
450                                                if (!properties.contains(next.getPropertyName())) {
451                                                        continue;
452                                                }
453                                        }
454
455                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
456                                        ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName());
457
458                                        if (next instanceof IContextValidationSupport.StringConceptProperty) {
459                                                IContextValidationSupport.StringConceptProperty prop = (IContextValidationSupport.StringConceptProperty) next;
460                                                ParametersUtil.addPartString(theContext, property, "value", prop.getValue());
461                                        } else if (next instanceof IContextValidationSupport.CodingConceptProperty) {
462                                                IContextValidationSupport.CodingConceptProperty prop = (IContextValidationSupport.CodingConceptProperty) next;
463                                                ParametersUtil.addPartCoding(theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay());
464                                        } else {
465                                                throw new IllegalStateException("Don't know how to handle " + next.getClass());
466                                        }
467                                }
468                        }
469
470                        if (myDesignations != null) {
471                                for (ConceptDesignation next : myDesignations) {
472
473                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
474                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
475                                        ParametersUtil.addPartCoding(theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
476                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
477                                }
478                        }
479
480                        return retVal;
481                }
482
483                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
484                        return new LookupCodeResult()
485                                .setFound(false)
486                                .setSearchedForSystem(theSearchedForSystem)
487                                .setSearchedForCode(theSearchedForCode);
488                }
489        }
490
491}