001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.context.support;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
025import ca.uhn.fhir.util.ParametersUtil;
026import ca.uhn.fhir.util.UrlUtil;
027import jakarta.annotation.Nonnull;
028import jakarta.annotation.Nullable;
029import org.apache.commons.lang3.Validate;
030import org.apache.commons.lang3.builder.EqualsBuilder;
031import org.apache.commons.lang3.builder.HashCodeBuilder;
032import org.apache.commons.lang3.builder.ToStringBuilder;
033import org.hl7.fhir.instance.model.api.IBase;
034import org.hl7.fhir.instance.model.api.IBaseCoding;
035import org.hl7.fhir.instance.model.api.IBaseParameters;
036import org.hl7.fhir.instance.model.api.IBaseResource;
037import org.hl7.fhir.instance.model.api.IIdType;
038import org.hl7.fhir.instance.model.api.IPrimitiveType;
039
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collections;
043import java.util.List;
044import java.util.Objects;
045import java.util.Set;
046import java.util.function.Supplier;
047import java.util.stream.Collectors;
048
049import static org.apache.commons.lang3.StringUtils.defaultString;
050import static org.apache.commons.lang3.StringUtils.isNotBlank;
051
052/**
053 * This interface is a version-independent representation of the
054 * various functions that can be provided by validation and terminology
055 * services.
056 * <p>
057 * This interface is invoked directly by internal parts of the HAPI FHIR API, including the
058 * Validator and the FHIRPath evaluator. It is used to supply artifacts required for validation
059 * (e.g. StructureDefinition resources, ValueSet resources, etc.) and also to provide
060 * terminology functions such as code validation, ValueSet expansion, etc.
061 * </p>
062 * <p>
063 * Implementations are not required to implement all of the functions
064 * in this interface; in fact it is expected that most won't. Any
065 * methods which are not implemented may simply return <code>null</code>
066 * and calling code is expected to be able to handle this. Generally, a
067 * series of implementations of this interface will be joined together using
068 * the
069 * <a href="https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html">ValidationSupportChain</a>
070 * class.
071 * </p>
072 * <p>
073 * See <a href="https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html">Validation Support Modules</a>
074 * for information on how to assemble and configure implementations of this interface. See also
075 * the <code>org.hl7.fhir.common.hapi.validation.support</code>
076 * <a href="./package-summary.html">package summary</a>
077 * in the <code>hapi-fhir-validation</code> module for many implementations of this interface.
078 * </p>
079 *
080 * @since 5.0.0
081 */
082public interface IValidationSupport {
083        String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/";
084        String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
085
086        /**
087         * Expands the given portion of a ValueSet
088         *
089         * @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
090         *                                    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.
091         * @param theExpansionOptions         If provided (can be <code>null</code>), contains options controlling the expansion
092         * @param theValueSetToExpand         The valueset that should be expanded
093         * @return The expansion, or null
094         */
095        @Nullable
096        default ValueSetExpansionOutcome expandValueSet(
097                        ValidationSupportContext theValidationSupportContext,
098                        @Nullable ValueSetExpansionOptions theExpansionOptions,
099                        @Nonnull IBaseResource theValueSetToExpand) {
100                return null;
101        }
102
103        /**
104         * Expands the given portion of a ValueSet by canonical URL.
105         *
106         * @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
107         *                                    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.
108         * @param theExpansionOptions         If provided (can be <code>null</code>), contains options controlling the expansion
109         * @param theValueSetUrlToExpand      The valueset that should be expanded
110         * @return The expansion, or null
111         * @throws ResourceNotFoundException If no ValueSet can be found with the given URL
112         * @since 6.0.0
113         */
114        @Nullable
115        default ValueSetExpansionOutcome expandValueSet(
116                        ValidationSupportContext theValidationSupportContext,
117                        @Nullable ValueSetExpansionOptions theExpansionOptions,
118                        @Nonnull String theValueSetUrlToExpand)
119                        throws ResourceNotFoundException {
120                Validate.notBlank(theValueSetUrlToExpand, "theValueSetUrlToExpand must not be null or blank");
121                IBaseResource valueSet =
122                                theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrlToExpand);
123                if (valueSet == null) {
124                        throw new ResourceNotFoundException(
125                                        Msg.code(2024) + "Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetUrlToExpand));
126                }
127                return expandValueSet(theValidationSupportContext, theExpansionOptions, valueSet);
128        }
129
130        /**
131         * Load and return all conformance resources associated with this
132         * validation support module. This method may return null if it doesn't
133         * make sense for a given module.
134         */
135        @Nullable
136        default List<IBaseResource> fetchAllConformanceResources() {
137                return null;
138        }
139
140        /**
141         * Load and return all possible search parameters
142         *
143         * @since 6.6.0
144         */
145        @Nullable
146        default <T extends IBaseResource> List<T> fetchAllSearchParameters() {
147                return null;
148        }
149
150        /**
151         * Load and return all possible structure definitions
152         */
153        @Nullable
154        default <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
155                return null;
156        }
157
158        /**
159         * Load and return all possible structure definitions aside from resource definitions themselves
160         */
161        @Nullable
162        default <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
163                List<T> retVal = fetchAllStructureDefinitions();
164                if (retVal != null) {
165                        List<T> newList = new ArrayList<>(retVal.size());
166                        for (T next : retVal) {
167                                String url = defaultString(getFhirContext().newTerser().getSinglePrimitiveValueOrNull(next, "url"));
168                                if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
169                                        String lastPart = url.substring(URL_PREFIX_STRUCTURE_DEFINITION.length());
170                                        if (getFhirContext().getResourceTypes().contains(lastPart)) {
171                                                continue;
172                                        }
173                                }
174
175                                newList.add(next);
176                        }
177
178                        retVal = newList;
179                }
180
181                return retVal;
182        }
183
184        /**
185         * Fetch a code system by ID
186         *
187         * @param theSystem The code system
188         * @return The valueset (must not be null, but can be an empty ValueSet)
189         */
190        @Nullable
191        default IBaseResource fetchCodeSystem(String theSystem) {
192                return null;
193        }
194
195        /**
196         * Loads a resource needed by the validation (a StructureDefinition, or a
197         * ValueSet)
198         *
199         * <p>
200         * Note: Since 5.3.0, {@literal theClass} can be {@literal null}
201         * </p>
202         *
203         * @param theClass The type of the resource to load, or <code>null</code> to return any resource with the given canonical URI
204         * @param theUri   The resource URI
205         * @return Returns the resource, or <code>null</code> if no resource with the
206         * given URI can be found
207         */
208        @SuppressWarnings("unchecked")
209        @Nullable
210        default <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
211                Validate.notBlank(theUri, "theUri must not be null or blank");
212
213                if (theClass == null) {
214                        Supplier<IBaseResource>[] sources = new Supplier[] {
215                                () -> fetchStructureDefinition(theUri), () -> fetchValueSet(theUri), () -> fetchCodeSystem(theUri)
216                        };
217                        return (T) Arrays.stream(sources)
218                                        .map(Supplier::get)
219                                        .filter(Objects::nonNull)
220                                        .findFirst()
221                                        .orElse(null);
222                }
223
224                switch (getFhirContext().getResourceType(theClass)) {
225                        case "StructureDefinition":
226                                return theClass.cast(fetchStructureDefinition(theUri));
227                        case "ValueSet":
228                                return theClass.cast(fetchValueSet(theUri));
229                        case "CodeSystem":
230                                return theClass.cast(fetchCodeSystem(theUri));
231                }
232
233                if (theUri.startsWith(URL_PREFIX_VALUE_SET)) {
234                        return theClass.cast(fetchValueSet(theUri));
235                }
236
237                return null;
238        }
239
240        @Nullable
241        default IBaseResource fetchStructureDefinition(String theUrl) {
242                return null;
243        }
244
245        /**
246         * Returns <code>true</code> if codes in the given code system can be expanded
247         * or validated
248         *
249         * @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
250         *                                    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.
251         * @param theSystem                   The URI for the code system, e.g. <code>"http://loinc.org"</code>
252         * @return Returns <code>true</code> if codes in the given code system can be
253         * validated
254         */
255        default boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
256                return false;
257        }
258
259        /**
260         * Returns <code>true</code> if a Remote Terminology Service is currently configured
261         *
262         * @return Returns <code>true</code> if a Remote Terminology Service is currently configured
263         */
264        default boolean isRemoteTerminologyServiceConfigured() {
265                return false;
266        }
267
268        /**
269         * Fetch the given ValueSet by URL, or returns null if one can't be found for the given URL
270         */
271        @Nullable
272        default IBaseResource fetchValueSet(String theValueSetUrl) {
273                return null;
274        }
275
276        /**
277         * Fetch the given binary data by key.
278         *
279         * @param binaryKey
280         * @return
281         */
282        default byte[] fetchBinary(String binaryKey) {
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 theOptions                  Provides options controlling the validation
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         * @return Returns a validation result object
298         */
299        @Nullable
300        default CodeValidationResult validateCode(
301                        ValidationSupportContext theValidationSupportContext,
302                        ConceptValidationOptions theOptions,
303                        String theCodeSystem,
304                        String theCode,
305                        String theDisplay,
306                        String theValueSetUrl) {
307                return null;
308        }
309
310        /**
311         * Validates that the given code exists and if possible returns a display
312         * name. This method is called to check codes which are found in "example"
313         * binding fields (e.g. <code>Observation.code</code>) in the default profile.
314         *
315         * @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
316         *                                    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.
317         * @param theCodeSystem               The code system, e.g. "<code>http://loinc.org</code>"
318         * @param theCode                     The code, e.g. "<code>1234-5</code>"
319         * @param theDisplay                  The display name, if it should also be validated
320         * @param theValueSet                 The ValueSet to validate against. Must not be null, and must be a ValueSet resource.
321         * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request
322         */
323        @Nullable
324        default CodeValidationResult validateCodeInValueSet(
325                        ValidationSupportContext theValidationSupportContext,
326                        ConceptValidationOptions theOptions,
327                        String theCodeSystem,
328                        String theCode,
329                        String theDisplay,
330                        @Nonnull IBaseResource theValueSet) {
331                return null;
332        }
333
334        /**
335         * Look up a code using the system and code value.
336         * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead.
337         *
338         * @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
339         *                                    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.
340         * @param theSystem                   The CodeSystem URL
341         * @param theCode                     The code
342         * @param theDisplayLanguage          Used to filter out the designation by the display language. To return all designation, set this value to <code>null</code>.
343         */
344        @Deprecated
345        @Nullable
346        default LookupCodeResult lookupCode(
347                        ValidationSupportContext theValidationSupportContext,
348                        String theSystem,
349                        String theCode,
350                        String theDisplayLanguage) {
351                return null;
352        }
353
354        /**
355         * Look up a code using the system and code value
356         * @deprecated This method has been deprecated in HAPI FHIR 7.0.0. Use {@link IValidationSupport#lookupCode(ValidationSupportContext, LookupCodeRequest)} instead.
357         *
358         * @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
359         *                                    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.
360         * @param theSystem                   The CodeSystem URL
361         * @param theCode                     The code
362         */
363        @Deprecated
364        @Nullable
365        default LookupCodeResult lookupCode(
366                        ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
367                return lookupCode(theValidationSupportContext, theSystem, theCode, null);
368        }
369
370        /**
371         * Look up a code using the system, code and other parameters captured in {@link LookupCodeRequest}.
372         * @since 7.0.0
373         *
374         * @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
375         *                                         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.
376         * @param theLookupCodeRequest             The parameters used to perform the lookup, including system and code.
377         */
378        @Nullable
379        default LookupCodeResult lookupCode(
380                        ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) {
381                // TODO: can change to return null once the deprecated methods are removed
382                return lookupCode(
383                                theValidationSupportContext,
384                                theLookupCodeRequest.getSystem(),
385                                theLookupCodeRequest.getCode(),
386                                theLookupCodeRequest.getDisplayLanguage());
387        }
388
389        /**
390         * Returns <code>true</code> if the given ValueSet can be validated by the given
391         * validation support module
392         *
393         * @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
394         *                                    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.
395         * @param theValueSetUrl              The ValueSet canonical URL
396         */
397        default boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
398                return false;
399        }
400
401        /**
402         * Generate a snapshot from the given differential profile.
403         *
404         * @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
405         *                                    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.
406         * @return Returns null if this module does not know how to handle this request
407         */
408        @Nullable
409        default IBaseResource generateSnapshot(
410                        ValidationSupportContext theValidationSupportContext,
411                        IBaseResource theInput,
412                        String theUrl,
413                        String theWebUrl,
414                        String theProfileName) {
415                return null;
416        }
417
418        /**
419         * Returns the FHIR Context associated with this module
420         */
421        FhirContext getFhirContext();
422
423        /**
424         * This method clears any temporary caches within the validation support. It is mainly intended for unit tests,
425         * but could be used in non-test scenarios as well.
426         */
427        default void invalidateCaches() {
428                // nothing
429        }
430
431        /**
432         * Attempt to translate the given concept from one code system to another
433         */
434        @Nullable
435        default TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
436                return null;
437        }
438
439        /**
440         * This field is used by the Terminology Troubleshooting Log to log which validation support module was used for the operation being logged.
441         */
442        default String getName() {
443                return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
444        }
445
446        /**
447         * Defines codes in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
448         */
449        /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
450        enum IssueSeverity {
451                /**
452                 * The issue caused the action to fail, and no further checking could be performed.
453                 */
454                FATAL("fatal"),
455                /**
456                 * The issue is sufficiently important to cause the action to fail.
457                 */
458                ERROR("error"),
459                /**
460                 * 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.
461                 */
462                WARNING("warning"),
463                /**
464                 * The issue has no relation to the degree of success of the action.
465                 */
466                INFORMATION("information"),
467                /**
468                 * The operation was successful.
469                 */
470                SUCCESS("success");
471                // the spec for OperationOutcome mentions that  a code from http://hl7.org/fhir/issue-severity is required
472
473                private final String myCode;
474
475                IssueSeverity(String theCode) {
476                        myCode = theCode;
477                }
478                /**
479                 * Provide mapping to a code in system <a href="http://hl7.org/fhir/issue-severity">http://hl7.org/fhir/issue-severity</a>.
480                 * @return the code
481                 */
482                public String getCode() {
483                        return myCode;
484                }
485                /**
486                 * Creates a {@link IssueSeverity} object from the given code.
487                 * @return the {@link IssueSeverity}
488                 */
489                public static IssueSeverity fromCode(String theCode) {
490                        switch (theCode) {
491                                case "fatal":
492                                        return FATAL;
493                                case "error":
494                                        return ERROR;
495                                case "warning":
496                                        return WARNING;
497                                case "information":
498                                        return INFORMATION;
499                                case "success":
500                                        return SUCCESS;
501                                default:
502                                        return null;
503                        }
504                }
505        }
506
507        /**
508         * Defines codes in system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
509         * The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown.
510         * Only a sub-set of these codes are defined as constants because they relate to validation,
511         * If there are additional ones that come up, for Remote Terminology they are currently supported via
512         * {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)}
513         * while for internal validators, more constants can be added to make things easier and consistent.
514         * This maps to resource OperationOutcome.issue.code.
515         */
516        /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
517        class CodeValidationIssueCode {
518                public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found");
519                public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid");
520                public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid");
521
522                private final String myCode;
523
524                // this is intentionally not exposed
525                CodeValidationIssueCode(String theCode) {
526                        myCode = theCode;
527                }
528
529                /**
530                 * Retrieve the corresponding code from system <a href="http://hl7.org/fhir/issue-type">http://hl7.org/fhir/issue-type</a>.
531                 * @return the code
532                 */
533                public String getCode() {
534                        return myCode;
535                }
536        }
537
538        /**
539         * Holds information about the details of a {@link CodeValidationIssue}.
540         * This maps to resource OperationOutcome.issue.details.
541         */
542        /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
543        class CodeValidationIssueDetails {
544                private final String myText;
545                private List<CodeValidationIssueCoding> myCodings;
546
547                public CodeValidationIssueDetails(String theText) {
548                        myText = theText;
549                }
550
551                // intentionally not exposed
552                void addCoding(CodeValidationIssueCoding theCoding) {
553                        getCodings().add(theCoding);
554                }
555
556                public CodeValidationIssueDetails addCoding(String theSystem, String theCode) {
557                        if (myCodings == null) {
558                                myCodings = new ArrayList<>();
559                        }
560                        myCodings.add(new CodeValidationIssueCoding(theSystem, theCode));
561                        return this;
562                }
563
564                public String getText() {
565                        return myText;
566                }
567
568                public List<CodeValidationIssueCoding> getCodings() {
569                        if (myCodings == null) {
570                                myCodings = new ArrayList<>();
571                        }
572                        return myCodings;
573                }
574        }
575
576        /**
577         * Defines codes that can be part of the details of an issue.
578         * There are some constants available (pre-defined) for codes for system <a href="http://hl7.org/fhir/tools/CodeSystem/tx-issue-type">http://hl7.org/fhir/tools/CodeSystem/tx-issue-type</a>.
579         * This maps to resource OperationOutcome.issue.details.coding[0].code.
580         */
581        class CodeValidationIssueCoding {
582                public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type";
583                public static CodeValidationIssueCoding VS_INVALID =
584                                new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid");
585                public static final CodeValidationIssueCoding NOT_FOUND =
586                                new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found");
587                public static final CodeValidationIssueCoding NOT_IN_VS =
588                                new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs");
589                public static final CodeValidationIssueCoding INVALID_CODE =
590                                new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code");
591                public static final CodeValidationIssueCoding INVALID_DISPLAY =
592                                new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display");
593                private final String mySystem, myCode;
594
595                // this is intentionally not exposed
596                CodeValidationIssueCoding(String theSystem, String theCode) {
597                        mySystem = theSystem;
598                        myCode = theCode;
599                }
600
601                /**
602                 * Retrieve the corresponding code for the details of a validation issue.
603                 * @return the code
604                 */
605                public String getCode() {
606                        return myCode;
607                }
608
609                /**
610                 * Retrieve the system for the details of a validation issue.
611                 * @return the system
612                 */
613                public String getSystem() {
614                        return mySystem;
615                }
616        }
617
618        /**
619         * This is a hapi-fhir internal version agnostic object holding information about a validation issue.
620         * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead.
621         */
622        class CodeValidationIssue {
623                private final String myDiagnostics;
624                private final IssueSeverity mySeverity;
625                private final CodeValidationIssueCode myCode;
626                private CodeValidationIssueDetails myDetails;
627
628                public CodeValidationIssue(
629                                String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) {
630                        this(theDiagnostics, theSeverity, theTypeCode, null);
631                }
632
633                public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) {
634                        this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null);
635                }
636
637                public CodeValidationIssue(
638                                String theDiagnostics,
639                                IssueSeverity theSeverity,
640                                CodeValidationIssueCode theType,
641                                CodeValidationIssueCoding theDetailsCoding) {
642                        myDiagnostics = theDiagnostics;
643                        mySeverity = theSeverity;
644                        myCode = theType;
645                        // reuse the diagnostics message as a detail text message
646                        myDetails = new CodeValidationIssueDetails(theDiagnostics);
647                        myDetails.addCoding(theDetailsCoding);
648                }
649
650                /**
651                 * @deprecated Please use {@link #getDiagnostics()} instead.
652                 */
653                @Deprecated(since = "7.4.6")
654                public String getMessage() {
655                        return getDiagnostics();
656                }
657
658                public String getDiagnostics() {
659                        return myDiagnostics;
660                }
661
662                public IssueSeverity getSeverity() {
663                        return mySeverity;
664                }
665
666                /**
667                 * @deprecated Please use {@link #getType()} instead.
668                 */
669                @Deprecated(since = "7.4.6")
670                public CodeValidationIssueCode getCode() {
671                        return getType();
672                }
673
674                public CodeValidationIssueCode getType() {
675                        return myCode;
676                }
677
678                /**
679                 * @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings.
680                 */
681                @Deprecated(since = "7.4.6")
682                public CodeValidationIssueCoding getCoding() {
683                        return myDetails != null
684                                        ? myDetails.getCodings().stream().findFirst().orElse(null)
685                                        : null;
686                }
687
688                public void setDetails(CodeValidationIssueDetails theDetails) {
689                        this.myDetails = theDetails;
690                }
691
692                public CodeValidationIssueDetails getDetails() {
693                        return myDetails;
694                }
695
696                public boolean hasIssueDetailCode(@Nonnull String theCode) {
697                        // this method is system agnostic at the moment but it can be restricted if needed
698                        return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode()));
699                }
700        }
701
702        class ConceptDesignation {
703
704                private String myLanguage;
705                private String myUseSystem;
706                private String myUseCode;
707                private String myUseDisplay;
708                private String myValue;
709
710                public String getLanguage() {
711                        return myLanguage;
712                }
713
714                public ConceptDesignation setLanguage(String theLanguage) {
715                        myLanguage = theLanguage;
716                        return this;
717                }
718
719                public String getUseSystem() {
720                        return myUseSystem;
721                }
722
723                public ConceptDesignation setUseSystem(String theUseSystem) {
724                        myUseSystem = theUseSystem;
725                        return this;
726                }
727
728                public String getUseCode() {
729                        return myUseCode;
730                }
731
732                public ConceptDesignation setUseCode(String theUseCode) {
733                        myUseCode = theUseCode;
734                        return this;
735                }
736
737                public String getUseDisplay() {
738                        return myUseDisplay;
739                }
740
741                public ConceptDesignation setUseDisplay(String theUseDisplay) {
742                        myUseDisplay = theUseDisplay;
743                        return this;
744                }
745
746                public String getValue() {
747                        return myValue;
748                }
749
750                public ConceptDesignation setValue(String theValue) {
751                        myValue = theValue;
752                        return this;
753                }
754        }
755
756        abstract class BaseConceptProperty {
757                private final String myPropertyName;
758
759                /**
760                 * Constructor
761                 */
762                protected BaseConceptProperty(String thePropertyName) {
763                        myPropertyName = thePropertyName;
764                }
765
766                public String getPropertyName() {
767                        return myPropertyName;
768                }
769
770                public abstract String getType();
771        }
772
773        // The reason these cannot be declared within an enum is because a Remote Terminology Service
774        // can support arbitrary types. We do not restrict against the types in the spec.
775        // Some of the types in the spec are not yet implemented as well.
776        // @see https://github.com/hapifhir/hapi-fhir/issues/5700
777        String TYPE_STRING = "string";
778        String TYPE_BOOLEAN = "boolean";
779        String TYPE_CODING = "Coding";
780        String TYPE_GROUP = "group";
781
782        class StringConceptProperty extends BaseConceptProperty {
783                private final String myValue;
784
785                /**
786                 * Constructor
787                 *
788                 * @param theName The name
789                 */
790                public StringConceptProperty(String theName, String theValue) {
791                        super(theName);
792                        myValue = theValue;
793                }
794
795                public String getValue() {
796                        return myValue;
797                }
798
799                @Override
800                public String getType() {
801                        return TYPE_STRING;
802                }
803        }
804
805        class BooleanConceptProperty extends BaseConceptProperty {
806                private final boolean myValue;
807
808                /**
809                 * Constructor
810                 *
811                 * @param theName The name
812                 */
813                public BooleanConceptProperty(String theName, boolean theValue) {
814                        super(theName);
815                        myValue = theValue;
816                }
817
818                public boolean getValue() {
819                        return myValue;
820                }
821
822                @Override
823                public String getType() {
824                        return TYPE_BOOLEAN;
825                }
826        }
827
828        class CodingConceptProperty extends BaseConceptProperty {
829                private final String myCode;
830                private final String myCodeSystem;
831                private final String myDisplay;
832
833                /**
834                 * Constructor
835                 *
836                 * @param theName The name
837                 */
838                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
839                        super(theName);
840                        myCodeSystem = theCodeSystem;
841                        myCode = theCode;
842                        myDisplay = theDisplay;
843                }
844
845                public String getCode() {
846                        return myCode;
847                }
848
849                public String getCodeSystem() {
850                        return myCodeSystem;
851                }
852
853                public String getDisplay() {
854                        return myDisplay;
855                }
856
857                @Override
858                public String getType() {
859                        return TYPE_CODING;
860                }
861        }
862
863        class GroupConceptProperty extends BaseConceptProperty {
864                public GroupConceptProperty(String thePropertyName) {
865                        super(thePropertyName);
866                }
867
868                private List<BaseConceptProperty> subProperties;
869
870                public BaseConceptProperty addSubProperty(BaseConceptProperty theProperty) {
871                        if (subProperties == null) {
872                                subProperties = new ArrayList<>();
873                        }
874                        subProperties.add(theProperty);
875                        return this;
876                }
877
878                public List<BaseConceptProperty> getSubProperties() {
879                        return subProperties != null ? subProperties : Collections.emptyList();
880                }
881
882                @Override
883                public String getType() {
884                        return TYPE_GROUP;
885                }
886        }
887
888        /**
889         * This is a hapi-fhir internal version agnostic object holding information about the validation result.
890         * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
891         */
892        class CodeValidationResult {
893                public static final String SOURCE_DETAILS = "sourceDetails";
894                public static final String RESULT = "result";
895                public static final String MESSAGE = "message";
896                public static final String DISPLAY = "display";
897
898                private String myCode;
899                private String myMessage;
900                private IssueSeverity mySeverity;
901                private String myCodeSystemName;
902                private String myCodeSystemVersion;
903                private List<BaseConceptProperty> myProperties;
904                private String myDisplay;
905                private String mySourceDetails;
906
907                private List<CodeValidationIssue> myIssues;
908
909                public CodeValidationResult() {
910                        super();
911                }
912
913                /**
914                 * This field may contain information about what the source of the
915                 * validation information was.
916                 */
917                public String getSourceDetails() {
918                        return mySourceDetails;
919                }
920
921                /**
922                 * This field may contain information about what the source of the
923                 * validation information was.
924                 */
925                public CodeValidationResult setSourceDetails(String theSourceDetails) {
926                        mySourceDetails = theSourceDetails;
927                        return this;
928                }
929
930                public String getDisplay() {
931                        return myDisplay;
932                }
933
934                public CodeValidationResult setDisplay(String theDisplay) {
935                        myDisplay = theDisplay;
936                        return this;
937                }
938
939                public String getCode() {
940                        return myCode;
941                }
942
943                public CodeValidationResult setCode(String theCode) {
944                        myCode = theCode;
945                        return this;
946                }
947
948                public String getCodeSystemName() {
949                        return myCodeSystemName;
950                }
951
952                public CodeValidationResult setCodeSystemName(String theCodeSystemName) {
953                        myCodeSystemName = theCodeSystemName;
954                        return this;
955                }
956
957                public String getCodeSystemVersion() {
958                        return myCodeSystemVersion;
959                }
960
961                public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) {
962                        myCodeSystemVersion = theCodeSystemVersion;
963                        return this;
964                }
965
966                public String getMessage() {
967                        return myMessage;
968                }
969
970                public CodeValidationResult setMessage(String theMessage) {
971                        myMessage = theMessage;
972                        return this;
973                }
974
975                public List<BaseConceptProperty> getProperties() {
976                        return myProperties;
977                }
978
979                public void setProperties(List<BaseConceptProperty> theProperties) {
980                        myProperties = theProperties;
981                }
982
983                public IssueSeverity getSeverity() {
984                        return mySeverity;
985                }
986
987                public CodeValidationResult setSeverity(IssueSeverity theSeverity) {
988                        mySeverity = theSeverity;
989                        return this;
990                }
991
992                /**
993                 * @deprecated Please use method {@link #getIssues()} instead.
994                 */
995                @Deprecated(since = "7.4.6")
996                public List<CodeValidationIssue> getCodeValidationIssues() {
997                        return getIssues();
998                }
999
1000                /**
1001                 * @deprecated Please use method {@link #setIssues(List)} instead.
1002                 */
1003                @Deprecated(since = "7.4.6")
1004                public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
1005                        return setIssues(theCodeValidationIssues);
1006                }
1007
1008                /**
1009                 * @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
1010                 */
1011                @Deprecated(since = "7.4.6")
1012                public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
1013                        getCodeValidationIssues().add(theCodeValidationIssue);
1014                        return this;
1015                }
1016
1017                public List<CodeValidationIssue> getIssues() {
1018                        if (myIssues == null) {
1019                                myIssues = new ArrayList<>();
1020                        }
1021                        return myIssues;
1022                }
1023
1024                public CodeValidationResult setIssues(List<CodeValidationIssue> theIssues) {
1025                        myIssues = new ArrayList<>(theIssues);
1026                        return this;
1027                }
1028
1029                public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
1030                        getIssues().add(theCodeValidationIssue);
1031                        return this;
1032                }
1033
1034                public boolean isOk() {
1035                        return isNotBlank(myCode);
1036                }
1037
1038                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
1039                        LookupCodeResult retVal = new LookupCodeResult();
1040                        retVal.setSearchedForSystem(theSearchedForSystem);
1041                        retVal.setSearchedForCode(theSearchedForCode);
1042                        if (isOk()) {
1043                                retVal.setFound(true);
1044                                retVal.setCodeDisplay(myDisplay);
1045                                retVal.setCodeSystemDisplayName(getCodeSystemName());
1046                                retVal.setCodeSystemVersion(getCodeSystemVersion());
1047                        }
1048                        return retVal;
1049                }
1050
1051                /**
1052                 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string
1053                 */
1054                public String getSeverityCode() {
1055                        String retVal = null;
1056                        if (getSeverity() != null) {
1057                                retVal = getSeverity().getCode();
1058                        }
1059                        return retVal;
1060                }
1061
1062                /**
1063                 * Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
1064                 * @param theSeverityCode the code
1065                 * @return the current {@link CodeValidationResult} instance
1066                 */
1067                @Deprecated(since = "7.4.6")
1068                public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
1069                        setSeverity(IssueSeverity.fromCode(theSeverityCode));
1070                        return this;
1071                }
1072
1073                public IBaseParameters toParameters(FhirContext theContext) {
1074                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
1075
1076                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk());
1077                        if (isNotBlank(getMessage())) {
1078                                ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage());
1079                        }
1080                        if (isNotBlank(getDisplay())) {
1081                                ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay());
1082                        }
1083                        if (isNotBlank(getSourceDetails())) {
1084                                ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
1085                        }
1086                        /*
1087                        should translate issues as well, except that is version specific code, so it requires more refactoring
1088                        or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
1089                        @see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
1090                        */
1091
1092                        return retVal;
1093                }
1094        }
1095
1096        class ValueSetExpansionOutcome {
1097
1098                private final IBaseResource myValueSet;
1099                private final String myError;
1100
1101                private final boolean myErrorIsFromServer;
1102
1103                public ValueSetExpansionOutcome(String theError, boolean theErrorIsFromServer) {
1104                        myValueSet = null;
1105                        myError = theError;
1106                        myErrorIsFromServer = theErrorIsFromServer;
1107                }
1108
1109                public ValueSetExpansionOutcome(IBaseResource theValueSet) {
1110                        myValueSet = theValueSet;
1111                        myError = null;
1112                        myErrorIsFromServer = false;
1113                }
1114
1115                public String getError() {
1116                        return myError;
1117                }
1118
1119                public IBaseResource getValueSet() {
1120                        return myValueSet;
1121                }
1122
1123                public boolean getErrorIsFromServer() {
1124                        return myErrorIsFromServer;
1125                }
1126        }
1127
1128        class LookupCodeResult {
1129
1130                private String myCodeDisplay;
1131                private boolean myCodeIsAbstract;
1132                private String myCodeSystemDisplayName;
1133                private String myCodeSystemVersion;
1134                private boolean myFound;
1135                private String mySearchedForCode;
1136                private String mySearchedForSystem;
1137                private List<BaseConceptProperty> myProperties;
1138                private List<ConceptDesignation> myDesignations;
1139                private String myErrorMessage;
1140
1141                /**
1142                 * Constructor
1143                 */
1144                public LookupCodeResult() {
1145                        super();
1146                }
1147
1148                public List<BaseConceptProperty> getProperties() {
1149                        if (myProperties == null) {
1150                                myProperties = new ArrayList<>();
1151                        }
1152                        return myProperties;
1153                }
1154
1155                public void setProperties(List<BaseConceptProperty> theProperties) {
1156                        myProperties = theProperties;
1157                }
1158
1159                @Nonnull
1160                public List<ConceptDesignation> getDesignations() {
1161                        if (myDesignations == null) {
1162                                myDesignations = new ArrayList<>();
1163                        }
1164                        return myDesignations;
1165                }
1166
1167                public String getCodeDisplay() {
1168                        return myCodeDisplay;
1169                }
1170
1171                public void setCodeDisplay(String theCodeDisplay) {
1172                        myCodeDisplay = theCodeDisplay;
1173                }
1174
1175                public String getCodeSystemDisplayName() {
1176                        return myCodeSystemDisplayName;
1177                }
1178
1179                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
1180                        myCodeSystemDisplayName = theCodeSystemDisplayName;
1181                }
1182
1183                public String getCodeSystemVersion() {
1184                        return myCodeSystemVersion;
1185                }
1186
1187                public void setCodeSystemVersion(String theCodeSystemVersion) {
1188                        myCodeSystemVersion = theCodeSystemVersion;
1189                }
1190
1191                public String getSearchedForCode() {
1192                        return mySearchedForCode;
1193                }
1194
1195                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
1196                        mySearchedForCode = theSearchedForCode;
1197                        return this;
1198                }
1199
1200                public String getSearchedForSystem() {
1201                        return mySearchedForSystem;
1202                }
1203
1204                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
1205                        mySearchedForSystem = theSearchedForSystem;
1206                        return this;
1207                }
1208
1209                public boolean isCodeIsAbstract() {
1210                        return myCodeIsAbstract;
1211                }
1212
1213                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
1214                        myCodeIsAbstract = theCodeIsAbstract;
1215                }
1216
1217                public boolean isFound() {
1218                        return myFound;
1219                }
1220
1221                public LookupCodeResult setFound(boolean theFound) {
1222                        myFound = theFound;
1223                        return this;
1224                }
1225
1226                public void throwNotFoundIfAppropriate() {
1227                        if (!isFound()) {
1228                                throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode()
1229                                                + "] in system[" + getSearchedForSystem() + "]");
1230                        }
1231                }
1232
1233                /**
1234                 * Converts the current LookupCodeResult instance into a IBaseParameters instance which is returned
1235                 * to the client of the $lookup operation.
1236                 * @param theContext the FHIR context used for running the operation
1237                 * @param thePropertyNamesToFilter the properties which are passed as parameter to filter the result.
1238                 * @return the output for the lookup operation.
1239                 */
1240                public IBaseParameters toParameters(
1241                                FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNamesToFilter) {
1242
1243                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
1244                        if (isNotBlank(getCodeSystemDisplayName())) {
1245                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
1246                        }
1247                        if (isNotBlank(getCodeSystemVersion())) {
1248                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
1249                        }
1250                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
1251                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
1252
1253                        if (myProperties != null) {
1254
1255                                final List<BaseConceptProperty> propertiesToReturn;
1256                                if (thePropertyNamesToFilter != null && !thePropertyNamesToFilter.isEmpty()) {
1257                                        // TODO MM: The logic to filter of properties could actually be moved to the lookupCode provider.
1258                                        // That is where the rest of the lookupCode input parameter handling is done.
1259                                        // This was left as is for now but can be done with next opportunity.
1260                                        Set<String> propertyNameList = thePropertyNamesToFilter.stream()
1261                                                        .map(IPrimitiveType::getValueAsString)
1262                                                        .collect(Collectors.toSet());
1263                                        propertiesToReturn = myProperties.stream()
1264                                                        .filter(p -> propertyNameList.contains(p.getPropertyName()))
1265                                                        .collect(Collectors.toList());
1266                                } else {
1267                                        propertiesToReturn = myProperties;
1268                                }
1269
1270                                for (BaseConceptProperty next : propertiesToReturn) {
1271                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
1272                                        populateProperty(theContext, property, next);
1273                                }
1274                        }
1275
1276                        if (myDesignations != null) {
1277                                for (ConceptDesignation next : myDesignations) {
1278                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
1279                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
1280                                        ParametersUtil.addPartCoding(
1281                                                        theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
1282                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
1283                                }
1284                        }
1285
1286                        return retVal;
1287                }
1288
1289                private void populateProperty(
1290                                FhirContext theContext, IBase theProperty, BaseConceptProperty theConceptProperty) {
1291                        ParametersUtil.addPartCode(theContext, theProperty, "code", theConceptProperty.getPropertyName());
1292                        String propertyType = theConceptProperty.getType();
1293                        switch (propertyType) {
1294                                case TYPE_STRING:
1295                                        StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty;
1296                                        ParametersUtil.addPartString(theContext, theProperty, "value", stringConceptProperty.getValue());
1297                                        break;
1298                                case TYPE_BOOLEAN:
1299                                        BooleanConceptProperty booleanConceptProperty = (BooleanConceptProperty) theConceptProperty;
1300                                        ParametersUtil.addPartBoolean(theContext, theProperty, "value", booleanConceptProperty.getValue());
1301                                        break;
1302                                case TYPE_CODING:
1303                                        CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty;
1304                                        ParametersUtil.addPartCoding(
1305                                                        theContext,
1306                                                        theProperty,
1307                                                        "value",
1308                                                        codingConceptProperty.getCodeSystem(),
1309                                                        codingConceptProperty.getCode(),
1310                                                        codingConceptProperty.getDisplay());
1311                                        break;
1312                                case TYPE_GROUP:
1313                                        GroupConceptProperty groupConceptProperty = (GroupConceptProperty) theConceptProperty;
1314                                        if (groupConceptProperty.getSubProperties().isEmpty()) {
1315                                                break;
1316                                        }
1317                                        groupConceptProperty.getSubProperties().forEach(p -> {
1318                                                IBase subProperty = ParametersUtil.addPart(theContext, theProperty, "subproperty", null);
1319                                                populateProperty(theContext, subProperty, p);
1320                                        });
1321                                        break;
1322                                default:
1323                                        throw new IllegalStateException(
1324                                                        Msg.code(1739) + "Don't know how to handle " + theConceptProperty.getClass());
1325                        }
1326                }
1327
1328                public LookupCodeResult setErrorMessage(String theErrorMessage) {
1329                        myErrorMessage = theErrorMessage;
1330                        return this;
1331                }
1332
1333                public String getErrorMessage() {
1334                        return myErrorMessage;
1335                }
1336
1337                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
1338                        return new LookupCodeResult()
1339                                        .setFound(false)
1340                                        .setSearchedForSystem(theSearchedForSystem)
1341                                        .setSearchedForCode(theSearchedForCode);
1342                }
1343        }
1344
1345        class TranslateCodeRequest {
1346                private final String myTargetSystemUrl;
1347                private final String myConceptMapUrl;
1348                private final String myConceptMapVersion;
1349                private final String mySourceValueSetUrl;
1350                private final String myTargetValueSetUrl;
1351                private final IIdType myResourceId;
1352                private final boolean myReverse;
1353                private final List<IBaseCoding> myCodings;
1354
1355                public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) {
1356                        myCodings = theCodings;
1357                        myTargetSystemUrl = theTargetSystemUrl;
1358                        myConceptMapUrl = null;
1359                        myConceptMapVersion = null;
1360                        mySourceValueSetUrl = null;
1361                        myTargetValueSetUrl = null;
1362                        myResourceId = null;
1363                        myReverse = false;
1364                }
1365
1366                public TranslateCodeRequest(
1367                                List<IBaseCoding> theCodings,
1368                                String theTargetSystemUrl,
1369                                String theConceptMapUrl,
1370                                String theConceptMapVersion,
1371                                String theSourceValueSetUrl,
1372                                String theTargetValueSetUrl,
1373                                IIdType theResourceId,
1374                                boolean theReverse) {
1375                        myCodings = theCodings;
1376                        myTargetSystemUrl = theTargetSystemUrl;
1377                        myConceptMapUrl = theConceptMapUrl;
1378                        myConceptMapVersion = theConceptMapVersion;
1379                        mySourceValueSetUrl = theSourceValueSetUrl;
1380                        myTargetValueSetUrl = theTargetValueSetUrl;
1381                        myResourceId = theResourceId;
1382                        myReverse = theReverse;
1383                }
1384
1385                @Override
1386                public boolean equals(Object theO) {
1387                        if (this == theO) {
1388                                return true;
1389                        }
1390
1391                        if (theO == null || getClass() != theO.getClass()) {
1392                                return false;
1393                        }
1394
1395                        TranslateCodeRequest that = (TranslateCodeRequest) theO;
1396
1397                        return new EqualsBuilder()
1398                                        .append(myCodings, that.myCodings)
1399                                        .append(myTargetSystemUrl, that.myTargetSystemUrl)
1400                                        .append(myConceptMapUrl, that.myConceptMapUrl)
1401                                        .append(myConceptMapVersion, that.myConceptMapVersion)
1402                                        .append(mySourceValueSetUrl, that.mySourceValueSetUrl)
1403                                        .append(myTargetValueSetUrl, that.myTargetValueSetUrl)
1404                                        .append(myResourceId, that.myResourceId)
1405                                        .append(myReverse, that.myReverse)
1406                                        .isEquals();
1407                }
1408
1409                @Override
1410                public int hashCode() {
1411                        return new HashCodeBuilder(17, 37)
1412                                        .append(myCodings)
1413                                        .append(myTargetSystemUrl)
1414                                        .append(myConceptMapUrl)
1415                                        .append(myConceptMapVersion)
1416                                        .append(mySourceValueSetUrl)
1417                                        .append(myTargetValueSetUrl)
1418                                        .append(myResourceId)
1419                                        .append(myReverse)
1420                                        .toHashCode();
1421                }
1422
1423                public List<IBaseCoding> getCodings() {
1424                        return myCodings;
1425                }
1426
1427                public String getTargetSystemUrl() {
1428                        return myTargetSystemUrl;
1429                }
1430
1431                public String getConceptMapUrl() {
1432                        return myConceptMapUrl;
1433                }
1434
1435                public String getConceptMapVersion() {
1436                        return myConceptMapVersion;
1437                }
1438
1439                public String getSourceValueSetUrl() {
1440                        return mySourceValueSetUrl;
1441                }
1442
1443                public String getTargetValueSetUrl() {
1444                        return myTargetValueSetUrl;
1445                }
1446
1447                public IIdType getResourceId() {
1448                        return myResourceId;
1449                }
1450
1451                public boolean isReverse() {
1452                        return myReverse;
1453                }
1454
1455                @Override
1456                public String toString() {
1457                        return new ToStringBuilder(this)
1458                                        .append("sourceValueSetUrl", mySourceValueSetUrl)
1459                                        .append("targetSystemUrl", myTargetSystemUrl)
1460                                        .append("targetValueSetUrl", myTargetValueSetUrl)
1461                                        .append("reverse", myReverse)
1462                                        .toString();
1463                }
1464        }
1465
1466        /**
1467         * When validating a CodeableConcept containing multiple codings, this method can be used to control whether
1468         * the validator requires all codings in the CodeableConcept to be valid in order to consider the
1469         * CodeableConcept valid.
1470         * <p>
1471         * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below
1472         * for the behaviour associated with each value.
1473         * </p>
1474         * <p>
1475         *   <ul>
1476         *     <li>If <code>false</code> (default setting) the validation for codings will return a positive result only if
1477         *     ALL codings are valid.</li>
1478         *         <li>If <code>true</code> the validation for codings will return a positive result if ANY codings are valid.
1479         *         </li>
1480         *        </ul>
1481         * </p>
1482         * @return true or false depending on the desired coding validation behaviour.
1483         */
1484        default boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
1485                return false;
1486        }
1487}