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