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