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        enum CodeValidationIssueCode {
463                NOT_FOUND,
464                CODE_INVALID,
465                INVALID,
466                OTHER
467        }
468
469        enum CodeValidationIssueCoding {
470                VS_INVALID,
471                NOT_FOUND,
472                NOT_IN_VS,
473
474                INVALID_CODE,
475                INVALID_DISPLAY,
476                OTHER
477        }
478
479        class CodeValidationIssue {
480
481                private final String myMessage;
482                private final IssueSeverity mySeverity;
483                private final CodeValidationIssueCode myCode;
484                private final CodeValidationIssueCoding myCoding;
485
486                public CodeValidationIssue(
487                                String theMessage,
488                                IssueSeverity mySeverity,
489                                CodeValidationIssueCode theCode,
490                                CodeValidationIssueCoding theCoding) {
491                        this.myMessage = theMessage;
492                        this.mySeverity = mySeverity;
493                        this.myCode = theCode;
494                        this.myCoding = theCoding;
495                }
496
497                public String getMessage() {
498                        return myMessage;
499                }
500
501                public IssueSeverity getSeverity() {
502                        return mySeverity;
503                }
504
505                public CodeValidationIssueCode getCode() {
506                        return myCode;
507                }
508
509                public CodeValidationIssueCoding getCoding() {
510                        return myCoding;
511                }
512        }
513
514        class ConceptDesignation {
515
516                private String myLanguage;
517                private String myUseSystem;
518                private String myUseCode;
519                private String myUseDisplay;
520                private String myValue;
521
522                public String getLanguage() {
523                        return myLanguage;
524                }
525
526                public ConceptDesignation setLanguage(String theLanguage) {
527                        myLanguage = theLanguage;
528                        return this;
529                }
530
531                public String getUseSystem() {
532                        return myUseSystem;
533                }
534
535                public ConceptDesignation setUseSystem(String theUseSystem) {
536                        myUseSystem = theUseSystem;
537                        return this;
538                }
539
540                public String getUseCode() {
541                        return myUseCode;
542                }
543
544                public ConceptDesignation setUseCode(String theUseCode) {
545                        myUseCode = theUseCode;
546                        return this;
547                }
548
549                public String getUseDisplay() {
550                        return myUseDisplay;
551                }
552
553                public ConceptDesignation setUseDisplay(String theUseDisplay) {
554                        myUseDisplay = theUseDisplay;
555                        return this;
556                }
557
558                public String getValue() {
559                        return myValue;
560                }
561
562                public ConceptDesignation setValue(String theValue) {
563                        myValue = theValue;
564                        return this;
565                }
566        }
567
568        abstract class BaseConceptProperty {
569                private final String myPropertyName;
570
571                /**
572                 * Constructor
573                 */
574                protected BaseConceptProperty(String thePropertyName) {
575                        myPropertyName = thePropertyName;
576                }
577
578                public String getPropertyName() {
579                        return myPropertyName;
580                }
581
582                public abstract String getType();
583        }
584
585        // The reason these cannot be declared within an enum is because a Remote Terminology Service
586        // can support arbitrary types. We do not restrict against the types in the spec.
587        // Some of the types in the spec are not yet implemented as well.
588        // @see https://github.com/hapifhir/hapi-fhir/issues/5700
589        String TYPE_STRING = "string";
590        String TYPE_CODING = "Coding";
591        String TYPE_GROUP = "group";
592
593        class StringConceptProperty extends BaseConceptProperty {
594                private final String myValue;
595
596                /**
597                 * Constructor
598                 *
599                 * @param theName The name
600                 */
601                public StringConceptProperty(String theName, String theValue) {
602                        super(theName);
603                        myValue = theValue;
604                }
605
606                public String getValue() {
607                        return myValue;
608                }
609
610                public String getType() {
611                        return TYPE_STRING;
612                }
613        }
614
615        class CodingConceptProperty extends BaseConceptProperty {
616                private final String myCode;
617                private final String myCodeSystem;
618                private final String myDisplay;
619
620                /**
621                 * Constructor
622                 *
623                 * @param theName The name
624                 */
625                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
626                        super(theName);
627                        myCodeSystem = theCodeSystem;
628                        myCode = theCode;
629                        myDisplay = theDisplay;
630                }
631
632                public String getCode() {
633                        return myCode;
634                }
635
636                public String getCodeSystem() {
637                        return myCodeSystem;
638                }
639
640                public String getDisplay() {
641                        return myDisplay;
642                }
643
644                public String getType() {
645                        return TYPE_CODING;
646                }
647        }
648
649        class GroupConceptProperty extends BaseConceptProperty {
650                public GroupConceptProperty(String thePropertyName) {
651                        super(thePropertyName);
652                }
653
654                private List<BaseConceptProperty> subProperties;
655
656                public BaseConceptProperty addSubProperty(BaseConceptProperty theProperty) {
657                        if (subProperties == null) {
658                                subProperties = new ArrayList<>();
659                        }
660                        subProperties.add(theProperty);
661                        return this;
662                }
663
664                public List<BaseConceptProperty> getSubProperties() {
665                        return subProperties != null ? subProperties : Collections.emptyList();
666                }
667
668                @Override
669                public String getType() {
670                        return TYPE_GROUP;
671                }
672        }
673
674        class CodeValidationResult {
675                public static final String SOURCE_DETAILS = "sourceDetails";
676                public static final String RESULT = "result";
677                public static final String MESSAGE = "message";
678                public static final String DISPLAY = "display";
679
680                private String myCode;
681                private String myMessage;
682                private IssueSeverity mySeverity;
683                private String myCodeSystemName;
684                private String myCodeSystemVersion;
685                private List<BaseConceptProperty> myProperties;
686                private String myDisplay;
687                private String mySourceDetails;
688
689                private List<CodeValidationIssue> myCodeValidationIssues;
690
691                public CodeValidationResult() {
692                        super();
693                }
694
695                /**
696                 * This field may contain information about what the source of the
697                 * validation information was.
698                 */
699                public String getSourceDetails() {
700                        return mySourceDetails;
701                }
702
703                /**
704                 * This field may contain information about what the source of the
705                 * validation information was.
706                 */
707                public CodeValidationResult setSourceDetails(String theSourceDetails) {
708                        mySourceDetails = theSourceDetails;
709                        return this;
710                }
711
712                public String getDisplay() {
713                        return myDisplay;
714                }
715
716                public CodeValidationResult setDisplay(String theDisplay) {
717                        myDisplay = theDisplay;
718                        return this;
719                }
720
721                public String getCode() {
722                        return myCode;
723                }
724
725                public CodeValidationResult setCode(String theCode) {
726                        myCode = theCode;
727                        return this;
728                }
729
730                String getCodeSystemName() {
731                        return myCodeSystemName;
732                }
733
734                public CodeValidationResult setCodeSystemName(String theCodeSystemName) {
735                        myCodeSystemName = theCodeSystemName;
736                        return this;
737                }
738
739                public String getCodeSystemVersion() {
740                        return myCodeSystemVersion;
741                }
742
743                public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) {
744                        myCodeSystemVersion = theCodeSystemVersion;
745                        return this;
746                }
747
748                public String getMessage() {
749                        return myMessage;
750                }
751
752                public CodeValidationResult setMessage(String theMessage) {
753                        myMessage = theMessage;
754                        return this;
755                }
756
757                public List<BaseConceptProperty> getProperties() {
758                        return myProperties;
759                }
760
761                public void setProperties(List<BaseConceptProperty> theProperties) {
762                        myProperties = theProperties;
763                }
764
765                public IssueSeverity getSeverity() {
766                        return mySeverity;
767                }
768
769                public CodeValidationResult setSeverity(IssueSeverity theSeverity) {
770                        mySeverity = theSeverity;
771                        return this;
772                }
773
774                public List<CodeValidationIssue> getCodeValidationIssues() {
775                        if (myCodeValidationIssues == null) {
776                                myCodeValidationIssues = new ArrayList<>();
777                        }
778                        return myCodeValidationIssues;
779                }
780
781                public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
782                        myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues);
783                        return this;
784                }
785
786                public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
787                        getCodeValidationIssues().add(theCodeValidationIssue);
788                        return this;
789                }
790
791                public boolean isOk() {
792                        return isNotBlank(myCode);
793                }
794
795                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
796                        LookupCodeResult retVal = new LookupCodeResult();
797                        retVal.setSearchedForSystem(theSearchedForSystem);
798                        retVal.setSearchedForCode(theSearchedForCode);
799                        if (isOk()) {
800                                retVal.setFound(true);
801                                retVal.setCodeDisplay(myDisplay);
802                                retVal.setCodeSystemDisplayName(getCodeSystemName());
803                                retVal.setCodeSystemVersion(getCodeSystemVersion());
804                        }
805                        return retVal;
806                }
807
808                /**
809                 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string
810                 */
811                public String getSeverityCode() {
812                        String retVal = null;
813                        if (getSeverity() != null) {
814                                retVal = getSeverity().name().toLowerCase();
815                        }
816                        return retVal;
817                }
818
819                /**
820                 * Sets an issue severity as a string code. Value must be the name of
821                 * one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
822                 */
823                public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
824                        setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
825                        return this;
826                }
827
828                public IBaseParameters toParameters(FhirContext theContext) {
829                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
830
831                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk());
832                        if (isNotBlank(getMessage())) {
833                                ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage());
834                        }
835                        if (isNotBlank(getDisplay())) {
836                                ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay());
837                        }
838                        if (isNotBlank(getSourceDetails())) {
839                                ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
840                        }
841
842                        return retVal;
843                }
844        }
845
846        class ValueSetExpansionOutcome {
847
848                private final IBaseResource myValueSet;
849                private final String myError;
850
851                private boolean myErrorIsFromServer;
852
853                public ValueSetExpansionOutcome(String theError, boolean theErrorIsFromServer) {
854                        myValueSet = null;
855                        myError = theError;
856                        myErrorIsFromServer = theErrorIsFromServer;
857                }
858
859                public ValueSetExpansionOutcome(IBaseResource theValueSet) {
860                        myValueSet = theValueSet;
861                        myError = null;
862                        myErrorIsFromServer = false;
863                }
864
865                public String getError() {
866                        return myError;
867                }
868
869                public IBaseResource getValueSet() {
870                        return myValueSet;
871                }
872
873                public boolean getErrorIsFromServer() {
874                        return myErrorIsFromServer;
875                }
876        }
877
878        class LookupCodeResult {
879
880                private String myCodeDisplay;
881                private boolean myCodeIsAbstract;
882                private String myCodeSystemDisplayName;
883                private String myCodeSystemVersion;
884                private boolean myFound;
885                private String mySearchedForCode;
886                private String mySearchedForSystem;
887                private List<BaseConceptProperty> myProperties;
888                private List<ConceptDesignation> myDesignations;
889                private String myErrorMessage;
890
891                /**
892                 * Constructor
893                 */
894                public LookupCodeResult() {
895                        super();
896                }
897
898                public List<BaseConceptProperty> getProperties() {
899                        if (myProperties == null) {
900                                myProperties = new ArrayList<>();
901                        }
902                        return myProperties;
903                }
904
905                public void setProperties(List<BaseConceptProperty> theProperties) {
906                        myProperties = theProperties;
907                }
908
909                @Nonnull
910                public List<ConceptDesignation> getDesignations() {
911                        if (myDesignations == null) {
912                                myDesignations = new ArrayList<>();
913                        }
914                        return myDesignations;
915                }
916
917                public String getCodeDisplay() {
918                        return myCodeDisplay;
919                }
920
921                public void setCodeDisplay(String theCodeDisplay) {
922                        myCodeDisplay = theCodeDisplay;
923                }
924
925                public String getCodeSystemDisplayName() {
926                        return myCodeSystemDisplayName;
927                }
928
929                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
930                        myCodeSystemDisplayName = theCodeSystemDisplayName;
931                }
932
933                public String getCodeSystemVersion() {
934                        return myCodeSystemVersion;
935                }
936
937                public void setCodeSystemVersion(String theCodeSystemVersion) {
938                        myCodeSystemVersion = theCodeSystemVersion;
939                }
940
941                public String getSearchedForCode() {
942                        return mySearchedForCode;
943                }
944
945                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
946                        mySearchedForCode = theSearchedForCode;
947                        return this;
948                }
949
950                public String getSearchedForSystem() {
951                        return mySearchedForSystem;
952                }
953
954                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
955                        mySearchedForSystem = theSearchedForSystem;
956                        return this;
957                }
958
959                public boolean isCodeIsAbstract() {
960                        return myCodeIsAbstract;
961                }
962
963                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
964                        myCodeIsAbstract = theCodeIsAbstract;
965                }
966
967                public boolean isFound() {
968                        return myFound;
969                }
970
971                public LookupCodeResult setFound(boolean theFound) {
972                        myFound = theFound;
973                        return this;
974                }
975
976                public void throwNotFoundIfAppropriate() {
977                        if (isFound() == false) {
978                                throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode()
979                                                + "] in system[" + getSearchedForSystem() + "]");
980                        }
981                }
982
983                /**
984                 * Converts the current LookupCodeResult instance into a IBaseParameters instance which is returned
985                 * to the client of the $lookup operation.
986                 * @param theContext the FHIR context used for running the operation
987                 * @param thePropertyNamesToFilter the properties which are passed as parameter to filter the result.
988                 * @return the output for the lookup operation.
989                 */
990                public IBaseParameters toParameters(
991                                FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNamesToFilter) {
992
993                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
994                        if (isNotBlank(getCodeSystemDisplayName())) {
995                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
996                        }
997                        if (isNotBlank(getCodeSystemVersion())) {
998                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
999                        }
1000                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
1001                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
1002
1003                        if (myProperties != null) {
1004
1005                                final List<BaseConceptProperty> propertiesToReturn;
1006                                if (thePropertyNamesToFilter != null && !thePropertyNamesToFilter.isEmpty()) {
1007                                        // TODO MM: The logic to filter of properties could actually be moved to the lookupCode provider.
1008                                        // That is where the rest of the lookupCode input parameter handling is done.
1009                                        // This was left as is for now but can be done with next opportunity.
1010                                        Set<String> propertyNameList = thePropertyNamesToFilter.stream()
1011                                                        .map(IPrimitiveType::getValueAsString)
1012                                                        .collect(Collectors.toSet());
1013                                        propertiesToReturn = myProperties.stream()
1014                                                        .filter(p -> propertyNameList.contains(p.getPropertyName()))
1015                                                        .collect(Collectors.toList());
1016                                } else {
1017                                        propertiesToReturn = myProperties;
1018                                }
1019
1020                                for (BaseConceptProperty next : propertiesToReturn) {
1021                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
1022                                        populateProperty(theContext, property, next);
1023                                }
1024                        }
1025
1026                        if (myDesignations != null) {
1027                                for (ConceptDesignation next : myDesignations) {
1028                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
1029                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
1030                                        ParametersUtil.addPartCoding(
1031                                                        theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
1032                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
1033                                }
1034                        }
1035
1036                        return retVal;
1037                }
1038
1039                private void populateProperty(
1040                                FhirContext theContext, IBase theProperty, BaseConceptProperty theConceptProperty) {
1041                        ParametersUtil.addPartCode(theContext, theProperty, "code", theConceptProperty.getPropertyName());
1042                        String propertyType = theConceptProperty.getType();
1043                        switch (propertyType) {
1044                                case TYPE_STRING:
1045                                        StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty;
1046                                        ParametersUtil.addPartString(theContext, theProperty, "value", stringConceptProperty.getValue());
1047                                        break;
1048                                case TYPE_CODING:
1049                                        CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty;
1050                                        ParametersUtil.addPartCoding(
1051                                                        theContext,
1052                                                        theProperty,
1053                                                        "value",
1054                                                        codingConceptProperty.getCodeSystem(),
1055                                                        codingConceptProperty.getCode(),
1056                                                        codingConceptProperty.getDisplay());
1057                                        break;
1058                                case TYPE_GROUP:
1059                                        GroupConceptProperty groupConceptProperty = (GroupConceptProperty) theConceptProperty;
1060                                        if (groupConceptProperty.getSubProperties().isEmpty()) {
1061                                                break;
1062                                        }
1063                                        groupConceptProperty.getSubProperties().forEach(p -> {
1064                                                IBase subProperty = ParametersUtil.addPart(theContext, theProperty, "subproperty", null);
1065                                                populateProperty(theContext, subProperty, p);
1066                                        });
1067                                        break;
1068                                default:
1069                                        throw new IllegalStateException(
1070                                                        Msg.code(1739) + "Don't know how to handle " + theConceptProperty.getClass());
1071                        }
1072                }
1073
1074                public void setErrorMessage(String theErrorMessage) {
1075                        myErrorMessage = theErrorMessage;
1076                }
1077
1078                public String getErrorMessage() {
1079                        return myErrorMessage;
1080                }
1081
1082                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
1083                        return new LookupCodeResult()
1084                                        .setFound(false)
1085                                        .setSearchedForSystem(theSearchedForSystem)
1086                                        .setSearchedForCode(theSearchedForCode);
1087                }
1088        }
1089
1090        class TranslateCodeRequest {
1091                private final String myTargetSystemUrl;
1092                private final String myConceptMapUrl;
1093                private final String myConceptMapVersion;
1094                private final String mySourceValueSetUrl;
1095                private final String myTargetValueSetUrl;
1096                private final IIdType myResourceId;
1097                private final boolean myReverse;
1098                private List<IBaseCoding> myCodings;
1099
1100                public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) {
1101                        myCodings = theCodings;
1102                        myTargetSystemUrl = theTargetSystemUrl;
1103                        myConceptMapUrl = null;
1104                        myConceptMapVersion = null;
1105                        mySourceValueSetUrl = null;
1106                        myTargetValueSetUrl = null;
1107                        myResourceId = null;
1108                        myReverse = false;
1109                }
1110
1111                public TranslateCodeRequest(
1112                                List<IBaseCoding> theCodings,
1113                                String theTargetSystemUrl,
1114                                String theConceptMapUrl,
1115                                String theConceptMapVersion,
1116                                String theSourceValueSetUrl,
1117                                String theTargetValueSetUrl,
1118                                IIdType theResourceId,
1119                                boolean theReverse) {
1120                        myCodings = theCodings;
1121                        myTargetSystemUrl = theTargetSystemUrl;
1122                        myConceptMapUrl = theConceptMapUrl;
1123                        myConceptMapVersion = theConceptMapVersion;
1124                        mySourceValueSetUrl = theSourceValueSetUrl;
1125                        myTargetValueSetUrl = theTargetValueSetUrl;
1126                        myResourceId = theResourceId;
1127                        myReverse = theReverse;
1128                }
1129
1130                @Override
1131                public boolean equals(Object theO) {
1132                        if (this == theO) {
1133                                return true;
1134                        }
1135
1136                        if (theO == null || getClass() != theO.getClass()) {
1137                                return false;
1138                        }
1139
1140                        TranslateCodeRequest that = (TranslateCodeRequest) theO;
1141
1142                        return new EqualsBuilder()
1143                                        .append(myCodings, that.myCodings)
1144                                        .append(myTargetSystemUrl, that.myTargetSystemUrl)
1145                                        .append(myConceptMapUrl, that.myConceptMapUrl)
1146                                        .append(myConceptMapVersion, that.myConceptMapVersion)
1147                                        .append(mySourceValueSetUrl, that.mySourceValueSetUrl)
1148                                        .append(myTargetValueSetUrl, that.myTargetValueSetUrl)
1149                                        .append(myResourceId, that.myResourceId)
1150                                        .append(myReverse, that.myReverse)
1151                                        .isEquals();
1152                }
1153
1154                @Override
1155                public int hashCode() {
1156                        return new HashCodeBuilder(17, 37)
1157                                        .append(myCodings)
1158                                        .append(myTargetSystemUrl)
1159                                        .append(myConceptMapUrl)
1160                                        .append(myConceptMapVersion)
1161                                        .append(mySourceValueSetUrl)
1162                                        .append(myTargetValueSetUrl)
1163                                        .append(myResourceId)
1164                                        .append(myReverse)
1165                                        .toHashCode();
1166                }
1167
1168                public List<IBaseCoding> getCodings() {
1169                        return myCodings;
1170                }
1171
1172                public String getTargetSystemUrl() {
1173                        return myTargetSystemUrl;
1174                }
1175
1176                public String getConceptMapUrl() {
1177                        return myConceptMapUrl;
1178                }
1179
1180                public String getConceptMapVersion() {
1181                        return myConceptMapVersion;
1182                }
1183
1184                public String getSourceValueSetUrl() {
1185                        return mySourceValueSetUrl;
1186                }
1187
1188                public String getTargetValueSetUrl() {
1189                        return myTargetValueSetUrl;
1190                }
1191
1192                public IIdType getResourceId() {
1193                        return myResourceId;
1194                }
1195
1196                public boolean isReverse() {
1197                        return myReverse;
1198                }
1199
1200                @Override
1201                public String toString() {
1202                        return new ToStringBuilder(this)
1203                                        .append("sourceValueSetUrl", mySourceValueSetUrl)
1204                                        .append("targetSystemUrl", myTargetSystemUrl)
1205                                        .append("targetValueSetUrl", myTargetValueSetUrl)
1206                                        .append("reverse", myReverse)
1207                                        .toString();
1208                }
1209        }
1210
1211        /**
1212         * <p
1213         * Warning: This method's behaviour and naming is preserved for backwards compatibility, BUT the actual naming and
1214         * function are not aligned.
1215         * </p
1216         * <p>
1217         * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below
1218         * for the behaviour associated with each value.
1219         * </p>
1220         * <p>
1221         *   <ul>
1222         *     <li>If <code>false</code> (default setting) the validation for codings will return a positive result only if
1223         *     ALL codings are valid.</li>
1224         *         <li>If <code>true</code> the validation for codings will return a positive result if ANY codings are valid.
1225         *         </li>
1226         *        </ul>
1227         * </p>
1228         * @return true or false depending on the desired coding validation behaviour.
1229         */
1230        default boolean isEnabledValidationForCodingsLogicalAnd() {
1231                return false;
1232        }
1233}