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