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