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