001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.context.support;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
025import ca.uhn.fhir.util.ParametersUtil;
026import ca.uhn.fhir.util.UrlUtil;
027import jakarta.annotation.Nonnull;
028import jakarta.annotation.Nullable;
029import org.apache.commons.lang3.Validate;
030import org.apache.commons.lang3.builder.EqualsBuilder;
031import org.apache.commons.lang3.builder.HashCodeBuilder;
032import org.apache.commons.lang3.builder.ToStringBuilder;
033import org.hl7.fhir.instance.model.api.IBase;
034import org.hl7.fhir.instance.model.api.IBaseCoding;
035import org.hl7.fhir.instance.model.api.IBaseParameters;
036import org.hl7.fhir.instance.model.api.IBaseResource;
037import org.hl7.fhir.instance.model.api.IIdType;
038import org.hl7.fhir.instance.model.api.IPrimitiveType;
039
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collections;
043import java.util.List;
044import java.util.Set;
045import java.util.function.Supplier;
046import java.util.stream.Collectors;
047
048import static org.apache.commons.lang3.StringUtils.defaultString;
049import static org.apache.commons.lang3.StringUtils.isNotBlank;
050
051/**
052 * This interface is a version-independent representation of the
053 * various functions that can be provided by validation and terminology
054 * services.
055 * <p>
056 * This interface is invoked directly by internal parts of the HAPI FHIR API, including the
057 * Validator and the FHIRPath evaluator. It is used to supply artifacts required for validation
058 * (e.g. StructureDefinition resources, ValueSet resources, etc.) and also to provide
059 * terminology functions such as code validation, ValueSet expansion, etc.
060 * </p>
061 * <p>
062 * Implementations are not required to implement all of the functions
063 * in this interface; in fact it is expected that most won't. Any
064 * methods which are not implemented may simply return <code>null</code>
065 * and calling code is expected to be able to handle this. Generally, a
066 * series of implementations of this interface will be joined together using
067 * the
068 * <a href="https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html">ValidationSupportChain</a>
069 * class.
070 * </p>
071 * <p>
072 * See <a href="https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html">Validation Support Modules</a>
073 * for information on how to assemble and configure implementations of this interface. See also
074 * the <code>org.hl7.fhir.common.hapi.validation.support</code>
075 * <a href="./package-summary.html">package summary</a>
076 * in the <code>hapi-fhir-validation</code> module for many implementations of this interface.
077 * </p>
078 *
079 * @since 5.0.0
080 */
081public interface IValidationSupport {
082        String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/";
083
084        /**
085         * Expands the given portion of a ValueSet
086         *
087         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
088         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
089         * @param theExpansionOptions         If provided (can be <code>null</code>), contains options controlling the expansion
090         * @param theValueSetToExpand         The valueset that should be expanded
091         * @return The expansion, or null
092         */
093        @Nullable
094        default ValueSetExpansionOutcome expandValueSet(
095                        ValidationSupportContext theValidationSupportContext,
096                        @Nullable ValueSetExpansionOptions theExpansionOptions,
097                        @Nonnull IBaseResource theValueSetToExpand) {
098                return null;
099        }
100
101        /**
102         * Expands the given portion of a ValueSet by canonical URL.
103         *
104         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
105         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
106         * @param theExpansionOptions         If provided (can be <code>null</code>), contains options controlling the expansion
107         * @param theValueSetUrlToExpand      The valueset that should be expanded
108         * @return The expansion, or null
109         * @throws ResourceNotFoundException If no ValueSet can be found with the given URL
110         * @since 6.0.0
111         */
112        @Nullable
113        default ValueSetExpansionOutcome expandValueSet(
114                        ValidationSupportContext theValidationSupportContext,
115                        @Nullable ValueSetExpansionOptions theExpansionOptions,
116                        @Nonnull String theValueSetUrlToExpand)
117                        throws ResourceNotFoundException {
118                Validate.notBlank(theValueSetUrlToExpand, "theValueSetUrlToExpand must not be null or blank");
119                IBaseResource valueSet = fetchValueSet(theValueSetUrlToExpand);
120                if (valueSet == null) {
121                        throw new ResourceNotFoundException(
122                                        Msg.code(2024) + "Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetUrlToExpand));
123                }
124                return expandValueSet(theValidationSupportContext, theExpansionOptions, valueSet);
125        }
126
127        /**
128         * Load and return all conformance resources associated with this
129         * validation support module. This method may return null if it doesn't
130         * make sense for a given module.
131         */
132        @Nullable
133        default List<IBaseResource> fetchAllConformanceResources() {
134                return null;
135        }
136
137        /**
138         * Load and return all possible search parameters
139         *
140         * @since 6.6.0
141         */
142        @Nullable
143        default <T extends IBaseResource> List<T> fetchAllSearchParameters() {
144                return null;
145        }
146
147        /**
148         * Load and return all possible structure definitions
149         */
150        @Nullable
151        default <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
152                return null;
153        }
154
155        /**
156         * Load and return all possible structure definitions aside from resource definitions themselves
157         */
158        @Nullable
159        default <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
160                List<T> retVal = fetchAllStructureDefinitions();
161                if (retVal != null) {
162                        List<T> newList = new ArrayList<>(retVal.size());
163                        for (T next : retVal) {
164                                String url = defaultString(getFhirContext().newTerser().getSinglePrimitiveValueOrNull(next, "url"));
165                                if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
166                                        String lastPart = url.substring("http://hl7.org/fhir/StructureDefinition/".length());
167                                        if (getFhirContext().getResourceTypes().contains(lastPart)) {
168                                                continue;
169                                        }
170                                }
171
172                                newList.add(next);
173                        }
174
175                        retVal = newList;
176                }
177
178                return retVal;
179        }
180
181        /**
182         * Fetch a code system by ID
183         *
184         * @param theSystem The code system
185         * @return The valueset (must not be null, but can be an empty ValueSet)
186         */
187        @Nullable
188        default IBaseResource fetchCodeSystem(String theSystem) {
189                return null;
190        }
191
192        /**
193         * Loads a resource needed by the validation (a StructureDefinition, or a
194         * ValueSet)
195         *
196         * <p>
197         * Note: Since 5.3.0, {@literal theClass} can be {@literal null}
198         * </p>
199         *
200         * @param theClass The type of the resource to load, or <code>null</code> to return any resource with the given canonical URI
201         * @param theUri   The resource URI
202         * @return Returns the resource, or <code>null</code> if no resource with the
203         * given URI can be found
204         */
205        @SuppressWarnings("unchecked")
206        @Nullable
207        default <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
208                Validate.notBlank(theUri, "theUri must not be null or blank");
209
210                if (theClass == null) {
211                        Supplier<IBaseResource>[] sources = new Supplier[] {
212                                () -> fetchStructureDefinition(theUri), () -> fetchValueSet(theUri), () -> fetchCodeSystem(theUri)
213                        };
214                        return (T) Arrays.stream(sources)
215                                        .map(t -> t.get())
216                                        .filter(t -> t != null)
217                                        .findFirst()
218                                        .orElse(null);
219                }
220
221                switch (getFhirContext().getResourceType(theClass)) {
222                        case "StructureDefinition":
223                                return theClass.cast(fetchStructureDefinition(theUri));
224                        case "ValueSet":
225                                return theClass.cast(fetchValueSet(theUri));
226                        case "CodeSystem":
227                                return theClass.cast(fetchCodeSystem(theUri));
228                }
229
230                if (theUri.startsWith(URL_PREFIX_VALUE_SET)) {
231                        return theClass.cast(fetchValueSet(theUri));
232                }
233
234                return null;
235        }
236
237        @Nullable
238        default IBaseResource fetchStructureDefinition(String theUrl) {
239                return null;
240        }
241
242        /**
243         * Returns <code>true</code> if codes in the given code system can be expanded
244         * or validated
245         *
246         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
247         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
248         * @param theSystem                   The URI for the code system, e.g. <code>"http://loinc.org"</code>
249         * @return Returns <code>true</code> if codes in the given code system can be
250         * validated
251         */
252        default boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
253                return false;
254        }
255
256        /**
257         * Returns <code>true</code> if a Remote Terminology Service is currently configured
258         *
259         * @return Returns <code>true</code> if a Remote Terminology Service is currently configured
260         */
261        default boolean isRemoteTerminologyServiceConfigured() {
262                return false;
263        }
264
265        /**
266         * Fetch the given ValueSet by URL, or returns null if one can't be found for the given URL
267         */
268        @Nullable
269        default IBaseResource fetchValueSet(String theValueSetUrl) {
270                return null;
271        }
272
273        /**
274         * Fetch the given binary data by key.
275         *
276         * @param binaryKey
277         * @return
278         */
279        default byte[] fetchBinary(String binaryKey) {
280                return null;
281        }
282
283        /**
284         * Validates that the given code exists and if possible returns a display
285         * name. This method is called to check codes which are found in "example"
286         * binding fields (e.g. <code>Observation.code</code>) in the default profile.
287         *
288         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
289         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
290         * @param theOptions                  Provides options controlling the validation
291         * @param theCodeSystem               The code system, e.g. "<code>http://loinc.org</code>"
292         * @param theCode                     The code, e.g. "<code>1234-5</code>"
293         * @param theDisplay                  The display name, if it should also be validated
294         * @return Returns a validation result object
295         */
296        @Nullable
297        default CodeValidationResult validateCode(
298                        ValidationSupportContext theValidationSupportContext,
299                        ConceptValidationOptions theOptions,
300                        String theCodeSystem,
301                        String theCode,
302                        String theDisplay,
303                        String theValueSetUrl) {
304                return null;
305        }
306
307        /**
308         * Validates that the given code exists and if possible returns a display
309         * name. This method is called to check codes which are found in "example"
310         * binding fields (e.g. <code>Observation.code</code>) in the default profile.
311         *
312         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
313         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
314         * @param theCodeSystem               The code system, e.g. "<code>http://loinc.org</code>"
315         * @param theCode                     The code, e.g. "<code>1234-5</code>"
316         * @param theDisplay                  The display name, if it should also be validated
317         * @param theValueSet                 The ValueSet to validate against. Must not be null, and must be a ValueSet resource.
318         * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request
319         */
320        @Nullable
321        default CodeValidationResult validateCodeInValueSet(
322                        ValidationSupportContext theValidationSupportContext,
323                        ConceptValidationOptions theOptions,
324                        String theCodeSystem,
325                        String theCode,
326                        String theDisplay,
327                        @Nonnull IBaseResource theValueSet) {
328                return null;
329        }
330
331        /**
332         * Look up a code using the system and code value.
333         * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead.
334         *
335         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
336         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
337         * @param theSystem                   The CodeSystem URL
338         * @param theCode                     The code
339         * @param theDisplayLanguage          Used to filter out the designation by the display language. To return all designation, set this value to <code>null</code>.
340         */
341        @Deprecated
342        @Nullable
343        default LookupCodeResult lookupCode(
344                        ValidationSupportContext theValidationSupportContext,
345                        String theSystem,
346                        String theCode,
347                        String theDisplayLanguage) {
348                return null;
349        }
350
351        /**
352         * Look up a code using the system and code value
353         * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead.
354         *
355         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
356         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
357         * @param theSystem                   The CodeSystem URL
358         * @param theCode                     The code
359         */
360        @Deprecated
361        @Nullable
362        default LookupCodeResult lookupCode(
363                        ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
364                return lookupCode(theValidationSupportContext, theSystem, theCode, null);
365        }
366
367        /**
368         * Look up a code using the system, code and other parameters captured in {@link LookupCodeRequest}.
369         * @since 7.0.0
370         *
371         * @param theValidationSupportContext      The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
372         *                                         other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
373         * @param theLookupCodeRequest             The parameters used to perform the lookup, including system and code.
374         */
375        @Nullable
376        default LookupCodeResult lookupCode(
377                        ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) {
378                // TODO: can change to return null once the deprecated methods are removed
379                return lookupCode(
380                                theValidationSupportContext,
381                                theLookupCodeRequest.getSystem(),
382                                theLookupCodeRequest.getCode(),
383                                theLookupCodeRequest.getDisplayLanguage());
384        }
385
386        /**
387         * Returns <code>true</code> if the given ValueSet can be validated by the given
388         * validation support module
389         *
390         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
391         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
392         * @param theValueSetUrl              The ValueSet canonical URL
393         */
394        default boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
395                return false;
396        }
397
398        /**
399         * Generate a snapshot from the given differential profile.
400         *
401         * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
402         *                                    other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
403         * @return Returns null if this module does not know how to handle this request
404         */
405        @Nullable
406        default IBaseResource generateSnapshot(
407                        ValidationSupportContext theValidationSupportContext,
408                        IBaseResource theInput,
409                        String theUrl,
410                        String theWebUrl,
411                        String theProfileName) {
412                return null;
413        }
414
415        /**
416         * Returns the FHIR Context associated with this module
417         */
418        FhirContext getFhirContext();
419
420        /**
421         * This method clears any temporary caches within the validation support. It is mainly intended for unit tests,
422         * but could be used in non-test scenarios as well.
423         */
424        default void invalidateCaches() {
425                // nothing
426        }
427
428        /**
429         * Attempt to translate the given concept from one code system to another
430         */
431        @Nullable
432        default TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
433                return null;
434        }
435
436        /**
437         * This field is used by the Terminology Troubleshooting Log to log which validation support module was used for the operation being logged.
438         */
439        default String getName() {
440                return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
441        }
442
443        enum IssueSeverity {
444                /**
445                 * The issue caused the action to fail, and no further checking could be performed.
446                 */
447                FATAL,
448                /**
449                 * The issue is sufficiently important to cause the action to fail.
450                 */
451                ERROR,
452                /**
453                 * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
454                 */
455                WARNING,
456                /**
457                 * The issue has no relation to the degree of success of the action.
458                 */
459                INFORMATION
460        }
461
462        class ConceptDesignation {
463
464                private String myLanguage;
465                private String myUseSystem;
466                private String myUseCode;
467                private String myUseDisplay;
468                private String myValue;
469
470                public String getLanguage() {
471                        return myLanguage;
472                }
473
474                public ConceptDesignation setLanguage(String theLanguage) {
475                        myLanguage = theLanguage;
476                        return this;
477                }
478
479                public String getUseSystem() {
480                        return myUseSystem;
481                }
482
483                public ConceptDesignation setUseSystem(String theUseSystem) {
484                        myUseSystem = theUseSystem;
485                        return this;
486                }
487
488                public String getUseCode() {
489                        return myUseCode;
490                }
491
492                public ConceptDesignation setUseCode(String theUseCode) {
493                        myUseCode = theUseCode;
494                        return this;
495                }
496
497                public String getUseDisplay() {
498                        return myUseDisplay;
499                }
500
501                public ConceptDesignation setUseDisplay(String theUseDisplay) {
502                        myUseDisplay = theUseDisplay;
503                        return this;
504                }
505
506                public String getValue() {
507                        return myValue;
508                }
509
510                public ConceptDesignation setValue(String theValue) {
511                        myValue = theValue;
512                        return this;
513                }
514        }
515
516        abstract class BaseConceptProperty {
517                private final String myPropertyName;
518
519                /**
520                 * Constructor
521                 */
522                protected BaseConceptProperty(String thePropertyName) {
523                        myPropertyName = thePropertyName;
524                }
525
526                public String getPropertyName() {
527                        return myPropertyName;
528                }
529
530                public abstract String getType();
531        }
532
533        String TYPE_STRING = "string";
534        String TYPE_CODING = "Coding";
535
536        class StringConceptProperty extends BaseConceptProperty {
537                private final String myValue;
538
539                /**
540                 * Constructor
541                 *
542                 * @param theName The name
543                 */
544                public StringConceptProperty(String theName, String theValue) {
545                        super(theName);
546                        myValue = theValue;
547                }
548
549                public String getValue() {
550                        return myValue;
551                }
552
553                public String getType() {
554                        return TYPE_STRING;
555                }
556        }
557
558        class CodingConceptProperty extends BaseConceptProperty {
559                private final String myCode;
560                private final String myCodeSystem;
561                private final String myDisplay;
562
563                /**
564                 * Constructor
565                 *
566                 * @param theName The name
567                 */
568                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
569                        super(theName);
570                        myCodeSystem = theCodeSystem;
571                        myCode = theCode;
572                        myDisplay = theDisplay;
573                }
574
575                public String getCode() {
576                        return myCode;
577                }
578
579                public String getCodeSystem() {
580                        return myCodeSystem;
581                }
582
583                public String getDisplay() {
584                        return myDisplay;
585                }
586
587                public String getType() {
588                        return TYPE_CODING;
589                }
590        }
591
592        class CodeValidationResult {
593                public static final String SOURCE_DETAILS = "sourceDetails";
594                public static final String RESULT = "result";
595                public static final String MESSAGE = "message";
596                public static final String DISPLAY = "display";
597
598                private String myCode;
599                private String myMessage;
600                private IssueSeverity mySeverity;
601                private String myCodeSystemName;
602                private String myCodeSystemVersion;
603                private List<BaseConceptProperty> myProperties;
604                private String myDisplay;
605                private String mySourceDetails;
606
607                public CodeValidationResult() {
608                        super();
609                }
610
611                /**
612                 * This field may contain information about what the source of the
613                 * validation information was.
614                 */
615                public String getSourceDetails() {
616                        return mySourceDetails;
617                }
618
619                /**
620                 * This field may contain information about what the source of the
621                 * validation information was.
622                 */
623                public CodeValidationResult setSourceDetails(String theSourceDetails) {
624                        mySourceDetails = theSourceDetails;
625                        return this;
626                }
627
628                public String getDisplay() {
629                        return myDisplay;
630                }
631
632                public CodeValidationResult setDisplay(String theDisplay) {
633                        myDisplay = theDisplay;
634                        return this;
635                }
636
637                public String getCode() {
638                        return myCode;
639                }
640
641                public CodeValidationResult setCode(String theCode) {
642                        myCode = theCode;
643                        return this;
644                }
645
646                String getCodeSystemName() {
647                        return myCodeSystemName;
648                }
649
650                public CodeValidationResult setCodeSystemName(String theCodeSystemName) {
651                        myCodeSystemName = theCodeSystemName;
652                        return this;
653                }
654
655                public String getCodeSystemVersion() {
656                        return myCodeSystemVersion;
657                }
658
659                public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) {
660                        myCodeSystemVersion = theCodeSystemVersion;
661                        return this;
662                }
663
664                public String getMessage() {
665                        return myMessage;
666                }
667
668                public CodeValidationResult setMessage(String theMessage) {
669                        myMessage = theMessage;
670                        return this;
671                }
672
673                public List<BaseConceptProperty> getProperties() {
674                        return myProperties;
675                }
676
677                public void setProperties(List<BaseConceptProperty> theProperties) {
678                        myProperties = theProperties;
679                }
680
681                public IssueSeverity getSeverity() {
682                        return mySeverity;
683                }
684
685                public CodeValidationResult setSeverity(IssueSeverity theSeverity) {
686                        mySeverity = theSeverity;
687                        return this;
688                }
689
690                public boolean isOk() {
691                        return isNotBlank(myCode);
692                }
693
694                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
695                        LookupCodeResult retVal = new LookupCodeResult();
696                        retVal.setSearchedForSystem(theSearchedForSystem);
697                        retVal.setSearchedForCode(theSearchedForCode);
698                        if (isOk()) {
699                                retVal.setFound(true);
700                                retVal.setCodeDisplay(myDisplay);
701                                retVal.setCodeSystemDisplayName(getCodeSystemName());
702                                retVal.setCodeSystemVersion(getCodeSystemVersion());
703                        }
704                        return retVal;
705                }
706
707                /**
708                 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string
709                 */
710                public String getSeverityCode() {
711                        String retVal = null;
712                        if (getSeverity() != null) {
713                                retVal = getSeverity().name().toLowerCase();
714                        }
715                        return retVal;
716                }
717
718                /**
719                 * Sets an issue severity as a string code. Value must be the name of
720                 * one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
721                 */
722                public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
723                        setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
724                        return this;
725                }
726
727                public IBaseParameters toParameters(FhirContext theContext) {
728                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
729
730                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk());
731                        if (isNotBlank(getMessage())) {
732                                ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage());
733                        }
734                        if (isNotBlank(getDisplay())) {
735                                ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay());
736                        }
737                        if (isNotBlank(getSourceDetails())) {
738                                ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
739                        }
740
741                        return retVal;
742                }
743        }
744
745        class ValueSetExpansionOutcome {
746
747                private final IBaseResource myValueSet;
748                private final String myError;
749
750                public ValueSetExpansionOutcome(String theError) {
751                        myValueSet = null;
752                        myError = theError;
753                }
754
755                public ValueSetExpansionOutcome(IBaseResource theValueSet) {
756                        myValueSet = theValueSet;
757                        myError = null;
758                }
759
760                public String getError() {
761                        return myError;
762                }
763
764                public IBaseResource getValueSet() {
765                        return myValueSet;
766                }
767        }
768
769        class LookupCodeResult {
770
771                private String myCodeDisplay;
772                private boolean myCodeIsAbstract;
773                private String myCodeSystemDisplayName;
774                private String myCodeSystemVersion;
775                private boolean myFound;
776                private String mySearchedForCode;
777                private String mySearchedForSystem;
778                private List<BaseConceptProperty> myProperties;
779                private List<ConceptDesignation> myDesignations;
780                private String myErrorMessage;
781
782                /**
783                 * Constructor
784                 */
785                public LookupCodeResult() {
786                        super();
787                }
788
789                public List<BaseConceptProperty> getProperties() {
790                        if (myProperties == null) {
791                                myProperties = new ArrayList<>();
792                        }
793                        return myProperties;
794                }
795
796                public void setProperties(List<BaseConceptProperty> theProperties) {
797                        myProperties = theProperties;
798                }
799
800                @Nonnull
801                public List<ConceptDesignation> getDesignations() {
802                        if (myDesignations == null) {
803                                myDesignations = new ArrayList<>();
804                        }
805                        return myDesignations;
806                }
807
808                public String getCodeDisplay() {
809                        return myCodeDisplay;
810                }
811
812                public void setCodeDisplay(String theCodeDisplay) {
813                        myCodeDisplay = theCodeDisplay;
814                }
815
816                public String getCodeSystemDisplayName() {
817                        return myCodeSystemDisplayName;
818                }
819
820                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
821                        myCodeSystemDisplayName = theCodeSystemDisplayName;
822                }
823
824                public String getCodeSystemVersion() {
825                        return myCodeSystemVersion;
826                }
827
828                public void setCodeSystemVersion(String theCodeSystemVersion) {
829                        myCodeSystemVersion = theCodeSystemVersion;
830                }
831
832                public String getSearchedForCode() {
833                        return mySearchedForCode;
834                }
835
836                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
837                        mySearchedForCode = theSearchedForCode;
838                        return this;
839                }
840
841                public String getSearchedForSystem() {
842                        return mySearchedForSystem;
843                }
844
845                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
846                        mySearchedForSystem = theSearchedForSystem;
847                        return this;
848                }
849
850                public boolean isCodeIsAbstract() {
851                        return myCodeIsAbstract;
852                }
853
854                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
855                        myCodeIsAbstract = theCodeIsAbstract;
856                }
857
858                public boolean isFound() {
859                        return myFound;
860                }
861
862                public LookupCodeResult setFound(boolean theFound) {
863                        myFound = theFound;
864                        return this;
865                }
866
867                public void throwNotFoundIfAppropriate() {
868                        if (isFound() == false) {
869                                throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode()
870                                                + "] in system[" + getSearchedForSystem() + "]");
871                        }
872                }
873
874                public IBaseParameters toParameters(
875                                FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNames) {
876
877                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
878                        if (isNotBlank(getCodeSystemDisplayName())) {
879                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
880                        }
881                        if (isNotBlank(getCodeSystemVersion())) {
882                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
883                        }
884                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
885                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
886
887                        if (myProperties != null) {
888
889                                Set<String> properties = Collections.emptySet();
890                                if (thePropertyNames != null) {
891                                        properties = thePropertyNames.stream()
892                                                        .map(IPrimitiveType::getValueAsString)
893                                                        .collect(Collectors.toSet());
894                                }
895
896                                for (BaseConceptProperty next : myProperties) {
897                                        String propertyName = next.getPropertyName();
898
899                                        if (!properties.isEmpty() && !properties.contains(propertyName)) {
900                                                continue;
901                                        }
902
903                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
904                                        ParametersUtil.addPartCode(theContext, property, "code", propertyName);
905
906                                        String propertyType = next.getType();
907                                        switch (propertyType) {
908                                                case TYPE_STRING:
909                                                        StringConceptProperty stringConceptProperty = (StringConceptProperty) next;
910                                                        ParametersUtil.addPartString(
911                                                                        theContext, property, "value", stringConceptProperty.getValue());
912                                                        break;
913                                                case TYPE_CODING:
914                                                        CodingConceptProperty codingConceptProperty = (CodingConceptProperty) next;
915                                                        ParametersUtil.addPartCoding(
916                                                                        theContext,
917                                                                        property,
918                                                                        "value",
919                                                                        codingConceptProperty.getCodeSystem(),
920                                                                        codingConceptProperty.getCode(),
921                                                                        codingConceptProperty.getDisplay());
922                                                        break;
923                                                default:
924                                                        throw new IllegalStateException(
925                                                                        Msg.code(1739) + "Don't know how to handle " + next.getClass());
926                                        }
927                                }
928                        }
929
930                        if (myDesignations != null) {
931                                for (ConceptDesignation next : myDesignations) {
932
933                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
934                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
935                                        ParametersUtil.addPartCoding(
936                                                        theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
937                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
938                                }
939                        }
940
941                        return retVal;
942                }
943
944                public void setErrorMessage(String theErrorMessage) {
945                        myErrorMessage = theErrorMessage;
946                }
947
948                public String getErrorMessage() {
949                        return myErrorMessage;
950                }
951
952                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
953                        return new LookupCodeResult()
954                                        .setFound(false)
955                                        .setSearchedForSystem(theSearchedForSystem)
956                                        .setSearchedForCode(theSearchedForCode);
957                }
958        }
959
960        class TranslateCodeRequest {
961                private final String myTargetSystemUrl;
962                private final String myConceptMapUrl;
963                private final String myConceptMapVersion;
964                private final String mySourceValueSetUrl;
965                private final String myTargetValueSetUrl;
966                private final IIdType myResourceId;
967                private final boolean myReverse;
968                private List<IBaseCoding> myCodings;
969
970                public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) {
971                        myCodings = theCodings;
972                        myTargetSystemUrl = theTargetSystemUrl;
973                        myConceptMapUrl = null;
974                        myConceptMapVersion = null;
975                        mySourceValueSetUrl = null;
976                        myTargetValueSetUrl = null;
977                        myResourceId = null;
978                        myReverse = false;
979                }
980
981                public TranslateCodeRequest(
982                                List<IBaseCoding> theCodings,
983                                String theTargetSystemUrl,
984                                String theConceptMapUrl,
985                                String theConceptMapVersion,
986                                String theSourceValueSetUrl,
987                                String theTargetValueSetUrl,
988                                IIdType theResourceId,
989                                boolean theReverse) {
990                        myCodings = theCodings;
991                        myTargetSystemUrl = theTargetSystemUrl;
992                        myConceptMapUrl = theConceptMapUrl;
993                        myConceptMapVersion = theConceptMapVersion;
994                        mySourceValueSetUrl = theSourceValueSetUrl;
995                        myTargetValueSetUrl = theTargetValueSetUrl;
996                        myResourceId = theResourceId;
997                        myReverse = theReverse;
998                }
999
1000                @Override
1001                public boolean equals(Object theO) {
1002                        if (this == theO) {
1003                                return true;
1004                        }
1005
1006                        if (theO == null || getClass() != theO.getClass()) {
1007                                return false;
1008                        }
1009
1010                        TranslateCodeRequest that = (TranslateCodeRequest) theO;
1011
1012                        return new EqualsBuilder()
1013                                        .append(myCodings, that.myCodings)
1014                                        .append(myTargetSystemUrl, that.myTargetSystemUrl)
1015                                        .append(myConceptMapUrl, that.myConceptMapUrl)
1016                                        .append(myConceptMapVersion, that.myConceptMapVersion)
1017                                        .append(mySourceValueSetUrl, that.mySourceValueSetUrl)
1018                                        .append(myTargetValueSetUrl, that.myTargetValueSetUrl)
1019                                        .append(myResourceId, that.myResourceId)
1020                                        .append(myReverse, that.myReverse)
1021                                        .isEquals();
1022                }
1023
1024                @Override
1025                public int hashCode() {
1026                        return new HashCodeBuilder(17, 37)
1027                                        .append(myCodings)
1028                                        .append(myTargetSystemUrl)
1029                                        .append(myConceptMapUrl)
1030                                        .append(myConceptMapVersion)
1031                                        .append(mySourceValueSetUrl)
1032                                        .append(myTargetValueSetUrl)
1033                                        .append(myResourceId)
1034                                        .append(myReverse)
1035                                        .toHashCode();
1036                }
1037
1038                public List<IBaseCoding> getCodings() {
1039                        return myCodings;
1040                }
1041
1042                public String getTargetSystemUrl() {
1043                        return myTargetSystemUrl;
1044                }
1045
1046                public String getConceptMapUrl() {
1047                        return myConceptMapUrl;
1048                }
1049
1050                public String getConceptMapVersion() {
1051                        return myConceptMapVersion;
1052                }
1053
1054                public String getSourceValueSetUrl() {
1055                        return mySourceValueSetUrl;
1056                }
1057
1058                public String getTargetValueSetUrl() {
1059                        return myTargetValueSetUrl;
1060                }
1061
1062                public IIdType getResourceId() {
1063                        return myResourceId;
1064                }
1065
1066                public boolean isReverse() {
1067                        return myReverse;
1068                }
1069
1070                @Override
1071                public String toString() {
1072                        return new ToStringBuilder(this)
1073                                        .append("sourceValueSetUrl", mySourceValueSetUrl)
1074                                        .append("targetSystemUrl", myTargetSystemUrl)
1075                                        .append("targetValueSetUrl", myTargetValueSetUrl)
1076                                        .append("reverse", myReverse)
1077                                        .toString();
1078                }
1079        }
1080
1081        /**
1082         * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation.
1083         * <p>
1084         * If true, validation for codings will return a positive result if all codings are valid.
1085         * If false, validation for codings will return a positive result if there is any coding that is valid.
1086         *
1087         * @return if the application has configured validation to use logical AND, as opposed to logical OR, which is the default
1088         */
1089        default boolean isEnabledValidationForCodingsLogicalAnd() {
1090                return false;
1091        }
1092}