001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 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_CODING = "Coding";
778        String TYPE_GROUP = "group";
779
780        class StringConceptProperty extends BaseConceptProperty {
781                private final String myValue;
782
783                /**
784                 * Constructor
785                 *
786                 * @param theName The name
787                 */
788                public StringConceptProperty(String theName, String theValue) {
789                        super(theName);
790                        myValue = theValue;
791                }
792
793                public String getValue() {
794                        return myValue;
795                }
796
797                @Override
798                public String getType() {
799                        return TYPE_STRING;
800                }
801        }
802
803        class CodingConceptProperty extends BaseConceptProperty {
804                private final String myCode;
805                private final String myCodeSystem;
806                private final String myDisplay;
807
808                /**
809                 * Constructor
810                 *
811                 * @param theName The name
812                 */
813                public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) {
814                        super(theName);
815                        myCodeSystem = theCodeSystem;
816                        myCode = theCode;
817                        myDisplay = theDisplay;
818                }
819
820                public String getCode() {
821                        return myCode;
822                }
823
824                public String getCodeSystem() {
825                        return myCodeSystem;
826                }
827
828                public String getDisplay() {
829                        return myDisplay;
830                }
831
832                @Override
833                public String getType() {
834                        return TYPE_CODING;
835                }
836        }
837
838        class GroupConceptProperty extends BaseConceptProperty {
839                public GroupConceptProperty(String thePropertyName) {
840                        super(thePropertyName);
841                }
842
843                private List<BaseConceptProperty> subProperties;
844
845                public BaseConceptProperty addSubProperty(BaseConceptProperty theProperty) {
846                        if (subProperties == null) {
847                                subProperties = new ArrayList<>();
848                        }
849                        subProperties.add(theProperty);
850                        return this;
851                }
852
853                public List<BaseConceptProperty> getSubProperties() {
854                        return subProperties != null ? subProperties : Collections.emptyList();
855                }
856
857                @Override
858                public String getType() {
859                        return TYPE_GROUP;
860                }
861        }
862
863        /**
864         * This is a hapi-fhir internal version agnostic object holding information about the validation result.
865         * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
866         */
867        class CodeValidationResult {
868                public static final String SOURCE_DETAILS = "sourceDetails";
869                public static final String RESULT = "result";
870                public static final String MESSAGE = "message";
871                public static final String DISPLAY = "display";
872
873                private String myCode;
874                private String myMessage;
875                private IssueSeverity mySeverity;
876                private String myCodeSystemName;
877                private String myCodeSystemVersion;
878                private List<BaseConceptProperty> myProperties;
879                private String myDisplay;
880                private String mySourceDetails;
881
882                private List<CodeValidationIssue> myIssues;
883
884                public CodeValidationResult() {
885                        super();
886                }
887
888                /**
889                 * This field may contain information about what the source of the
890                 * validation information was.
891                 */
892                public String getSourceDetails() {
893                        return mySourceDetails;
894                }
895
896                /**
897                 * This field may contain information about what the source of the
898                 * validation information was.
899                 */
900                public CodeValidationResult setSourceDetails(String theSourceDetails) {
901                        mySourceDetails = theSourceDetails;
902                        return this;
903                }
904
905                public String getDisplay() {
906                        return myDisplay;
907                }
908
909                public CodeValidationResult setDisplay(String theDisplay) {
910                        myDisplay = theDisplay;
911                        return this;
912                }
913
914                public String getCode() {
915                        return myCode;
916                }
917
918                public CodeValidationResult setCode(String theCode) {
919                        myCode = theCode;
920                        return this;
921                }
922
923                public String getCodeSystemName() {
924                        return myCodeSystemName;
925                }
926
927                public CodeValidationResult setCodeSystemName(String theCodeSystemName) {
928                        myCodeSystemName = theCodeSystemName;
929                        return this;
930                }
931
932                public String getCodeSystemVersion() {
933                        return myCodeSystemVersion;
934                }
935
936                public CodeValidationResult setCodeSystemVersion(String theCodeSystemVersion) {
937                        myCodeSystemVersion = theCodeSystemVersion;
938                        return this;
939                }
940
941                public String getMessage() {
942                        return myMessage;
943                }
944
945                public CodeValidationResult setMessage(String theMessage) {
946                        myMessage = theMessage;
947                        return this;
948                }
949
950                public List<BaseConceptProperty> getProperties() {
951                        return myProperties;
952                }
953
954                public void setProperties(List<BaseConceptProperty> theProperties) {
955                        myProperties = theProperties;
956                }
957
958                public IssueSeverity getSeverity() {
959                        return mySeverity;
960                }
961
962                public CodeValidationResult setSeverity(IssueSeverity theSeverity) {
963                        mySeverity = theSeverity;
964                        return this;
965                }
966
967                /**
968                 * @deprecated Please use method {@link #getIssues()} instead.
969                 */
970                @Deprecated(since = "7.4.6")
971                public List<CodeValidationIssue> getCodeValidationIssues() {
972                        return getIssues();
973                }
974
975                /**
976                 * @deprecated Please use method {@link #setIssues(List)} instead.
977                 */
978                @Deprecated(since = "7.4.6")
979                public CodeValidationResult setCodeValidationIssues(List<CodeValidationIssue> theCodeValidationIssues) {
980                        return setIssues(theCodeValidationIssues);
981                }
982
983                /**
984                 * @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
985                 */
986                @Deprecated(since = "7.4.6")
987                public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
988                        getCodeValidationIssues().add(theCodeValidationIssue);
989                        return this;
990                }
991
992                public List<CodeValidationIssue> getIssues() {
993                        if (myIssues == null) {
994                                myIssues = new ArrayList<>();
995                        }
996                        return myIssues;
997                }
998
999                public CodeValidationResult setIssues(List<CodeValidationIssue> theIssues) {
1000                        myIssues = new ArrayList<>(theIssues);
1001                        return this;
1002                }
1003
1004                public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
1005                        getIssues().add(theCodeValidationIssue);
1006                        return this;
1007                }
1008
1009                public boolean isOk() {
1010                        return isNotBlank(myCode);
1011                }
1012
1013                public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) {
1014                        LookupCodeResult retVal = new LookupCodeResult();
1015                        retVal.setSearchedForSystem(theSearchedForSystem);
1016                        retVal.setSearchedForCode(theSearchedForCode);
1017                        if (isOk()) {
1018                                retVal.setFound(true);
1019                                retVal.setCodeDisplay(myDisplay);
1020                                retVal.setCodeSystemDisplayName(getCodeSystemName());
1021                                retVal.setCodeSystemVersion(getCodeSystemVersion());
1022                        }
1023                        return retVal;
1024                }
1025
1026                /**
1027                 * Convenience method that returns {@link #getSeverity()} as an IssueSeverity code string
1028                 */
1029                public String getSeverityCode() {
1030                        String retVal = null;
1031                        if (getSeverity() != null) {
1032                                retVal = getSeverity().getCode();
1033                        }
1034                        return retVal;
1035                }
1036
1037                /**
1038                 * Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
1039                 * @param theSeverityCode the code
1040                 * @return the current {@link CodeValidationResult} instance
1041                 */
1042                @Deprecated(since = "7.4.6")
1043                public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
1044                        setSeverity(IssueSeverity.fromCode(theSeverityCode));
1045                        return this;
1046                }
1047
1048                public IBaseParameters toParameters(FhirContext theContext) {
1049                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
1050
1051                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, RESULT, isOk());
1052                        if (isNotBlank(getMessage())) {
1053                                ParametersUtil.addParameterToParametersString(theContext, retVal, MESSAGE, getMessage());
1054                        }
1055                        if (isNotBlank(getDisplay())) {
1056                                ParametersUtil.addParameterToParametersString(theContext, retVal, DISPLAY, getDisplay());
1057                        }
1058                        if (isNotBlank(getSourceDetails())) {
1059                                ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
1060                        }
1061                        /*
1062                        should translate issues as well, except that is version specific code, so it requires more refactoring
1063                        or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
1064                        @see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
1065                        */
1066
1067                        return retVal;
1068                }
1069        }
1070
1071        class ValueSetExpansionOutcome {
1072
1073                private final IBaseResource myValueSet;
1074                private final String myError;
1075
1076                private boolean myErrorIsFromServer;
1077
1078                public ValueSetExpansionOutcome(String theError, boolean theErrorIsFromServer) {
1079                        myValueSet = null;
1080                        myError = theError;
1081                        myErrorIsFromServer = theErrorIsFromServer;
1082                }
1083
1084                public ValueSetExpansionOutcome(IBaseResource theValueSet) {
1085                        myValueSet = theValueSet;
1086                        myError = null;
1087                        myErrorIsFromServer = false;
1088                }
1089
1090                public String getError() {
1091                        return myError;
1092                }
1093
1094                public IBaseResource getValueSet() {
1095                        return myValueSet;
1096                }
1097
1098                public boolean getErrorIsFromServer() {
1099                        return myErrorIsFromServer;
1100                }
1101        }
1102
1103        class LookupCodeResult {
1104
1105                private String myCodeDisplay;
1106                private boolean myCodeIsAbstract;
1107                private String myCodeSystemDisplayName;
1108                private String myCodeSystemVersion;
1109                private boolean myFound;
1110                private String mySearchedForCode;
1111                private String mySearchedForSystem;
1112                private List<BaseConceptProperty> myProperties;
1113                private List<ConceptDesignation> myDesignations;
1114                private String myErrorMessage;
1115
1116                /**
1117                 * Constructor
1118                 */
1119                public LookupCodeResult() {
1120                        super();
1121                }
1122
1123                public List<BaseConceptProperty> getProperties() {
1124                        if (myProperties == null) {
1125                                myProperties = new ArrayList<>();
1126                        }
1127                        return myProperties;
1128                }
1129
1130                public void setProperties(List<BaseConceptProperty> theProperties) {
1131                        myProperties = theProperties;
1132                }
1133
1134                @Nonnull
1135                public List<ConceptDesignation> getDesignations() {
1136                        if (myDesignations == null) {
1137                                myDesignations = new ArrayList<>();
1138                        }
1139                        return myDesignations;
1140                }
1141
1142                public String getCodeDisplay() {
1143                        return myCodeDisplay;
1144                }
1145
1146                public void setCodeDisplay(String theCodeDisplay) {
1147                        myCodeDisplay = theCodeDisplay;
1148                }
1149
1150                public String getCodeSystemDisplayName() {
1151                        return myCodeSystemDisplayName;
1152                }
1153
1154                public void setCodeSystemDisplayName(String theCodeSystemDisplayName) {
1155                        myCodeSystemDisplayName = theCodeSystemDisplayName;
1156                }
1157
1158                public String getCodeSystemVersion() {
1159                        return myCodeSystemVersion;
1160                }
1161
1162                public void setCodeSystemVersion(String theCodeSystemVersion) {
1163                        myCodeSystemVersion = theCodeSystemVersion;
1164                }
1165
1166                public String getSearchedForCode() {
1167                        return mySearchedForCode;
1168                }
1169
1170                public LookupCodeResult setSearchedForCode(String theSearchedForCode) {
1171                        mySearchedForCode = theSearchedForCode;
1172                        return this;
1173                }
1174
1175                public String getSearchedForSystem() {
1176                        return mySearchedForSystem;
1177                }
1178
1179                public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) {
1180                        mySearchedForSystem = theSearchedForSystem;
1181                        return this;
1182                }
1183
1184                public boolean isCodeIsAbstract() {
1185                        return myCodeIsAbstract;
1186                }
1187
1188                public void setCodeIsAbstract(boolean theCodeIsAbstract) {
1189                        myCodeIsAbstract = theCodeIsAbstract;
1190                }
1191
1192                public boolean isFound() {
1193                        return myFound;
1194                }
1195
1196                public LookupCodeResult setFound(boolean theFound) {
1197                        myFound = theFound;
1198                        return this;
1199                }
1200
1201                public void throwNotFoundIfAppropriate() {
1202                        if (isFound() == false) {
1203                                throw new ResourceNotFoundException(Msg.code(1738) + "Unable to find code[" + getSearchedForCode()
1204                                                + "] in system[" + getSearchedForSystem() + "]");
1205                        }
1206                }
1207
1208                /**
1209                 * Converts the current LookupCodeResult instance into a IBaseParameters instance which is returned
1210                 * to the client of the $lookup operation.
1211                 * @param theContext the FHIR context used for running the operation
1212                 * @param thePropertyNamesToFilter the properties which are passed as parameter to filter the result.
1213                 * @return the output for the lookup operation.
1214                 */
1215                public IBaseParameters toParameters(
1216                                FhirContext theContext, List<? extends IPrimitiveType<String>> thePropertyNamesToFilter) {
1217
1218                        IBaseParameters retVal = ParametersUtil.newInstance(theContext);
1219                        if (isNotBlank(getCodeSystemDisplayName())) {
1220                                ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName());
1221                        }
1222                        if (isNotBlank(getCodeSystemVersion())) {
1223                                ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion());
1224                        }
1225                        ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay());
1226                        ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract());
1227
1228                        if (myProperties != null) {
1229
1230                                final List<BaseConceptProperty> propertiesToReturn;
1231                                if (thePropertyNamesToFilter != null && !thePropertyNamesToFilter.isEmpty()) {
1232                                        // TODO MM: The logic to filter of properties could actually be moved to the lookupCode provider.
1233                                        // That is where the rest of the lookupCode input parameter handling is done.
1234                                        // This was left as is for now but can be done with next opportunity.
1235                                        Set<String> propertyNameList = thePropertyNamesToFilter.stream()
1236                                                        .map(IPrimitiveType::getValueAsString)
1237                                                        .collect(Collectors.toSet());
1238                                        propertiesToReturn = myProperties.stream()
1239                                                        .filter(p -> propertyNameList.contains(p.getPropertyName()))
1240                                                        .collect(Collectors.toList());
1241                                } else {
1242                                        propertiesToReturn = myProperties;
1243                                }
1244
1245                                for (BaseConceptProperty next : propertiesToReturn) {
1246                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property");
1247                                        populateProperty(theContext, property, next);
1248                                }
1249                        }
1250
1251                        if (myDesignations != null) {
1252                                for (ConceptDesignation next : myDesignations) {
1253                                        IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation");
1254                                        ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage());
1255                                        ParametersUtil.addPartCoding(
1256                                                        theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay());
1257                                        ParametersUtil.addPartString(theContext, property, "value", next.getValue());
1258                                }
1259                        }
1260
1261                        return retVal;
1262                }
1263
1264                private void populateProperty(
1265                                FhirContext theContext, IBase theProperty, BaseConceptProperty theConceptProperty) {
1266                        ParametersUtil.addPartCode(theContext, theProperty, "code", theConceptProperty.getPropertyName());
1267                        String propertyType = theConceptProperty.getType();
1268                        switch (propertyType) {
1269                                case TYPE_STRING:
1270                                        StringConceptProperty stringConceptProperty = (StringConceptProperty) theConceptProperty;
1271                                        ParametersUtil.addPartString(theContext, theProperty, "value", stringConceptProperty.getValue());
1272                                        break;
1273                                case TYPE_CODING:
1274                                        CodingConceptProperty codingConceptProperty = (CodingConceptProperty) theConceptProperty;
1275                                        ParametersUtil.addPartCoding(
1276                                                        theContext,
1277                                                        theProperty,
1278                                                        "value",
1279                                                        codingConceptProperty.getCodeSystem(),
1280                                                        codingConceptProperty.getCode(),
1281                                                        codingConceptProperty.getDisplay());
1282                                        break;
1283                                case TYPE_GROUP:
1284                                        GroupConceptProperty groupConceptProperty = (GroupConceptProperty) theConceptProperty;
1285                                        if (groupConceptProperty.getSubProperties().isEmpty()) {
1286                                                break;
1287                                        }
1288                                        groupConceptProperty.getSubProperties().forEach(p -> {
1289                                                IBase subProperty = ParametersUtil.addPart(theContext, theProperty, "subproperty", null);
1290                                                populateProperty(theContext, subProperty, p);
1291                                        });
1292                                        break;
1293                                default:
1294                                        throw new IllegalStateException(
1295                                                        Msg.code(1739) + "Don't know how to handle " + theConceptProperty.getClass());
1296                        }
1297                }
1298
1299                public LookupCodeResult setErrorMessage(String theErrorMessage) {
1300                        myErrorMessage = theErrorMessage;
1301                        return this;
1302                }
1303
1304                public String getErrorMessage() {
1305                        return myErrorMessage;
1306                }
1307
1308                public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) {
1309                        return new LookupCodeResult()
1310                                        .setFound(false)
1311                                        .setSearchedForSystem(theSearchedForSystem)
1312                                        .setSearchedForCode(theSearchedForCode);
1313                }
1314        }
1315
1316        class TranslateCodeRequest {
1317                private final String myTargetSystemUrl;
1318                private final String myConceptMapUrl;
1319                private final String myConceptMapVersion;
1320                private final String mySourceValueSetUrl;
1321                private final String myTargetValueSetUrl;
1322                private final IIdType myResourceId;
1323                private final boolean myReverse;
1324                private List<IBaseCoding> myCodings;
1325
1326                public TranslateCodeRequest(List<IBaseCoding> theCodings, String theTargetSystemUrl) {
1327                        myCodings = theCodings;
1328                        myTargetSystemUrl = theTargetSystemUrl;
1329                        myConceptMapUrl = null;
1330                        myConceptMapVersion = null;
1331                        mySourceValueSetUrl = null;
1332                        myTargetValueSetUrl = null;
1333                        myResourceId = null;
1334                        myReverse = false;
1335                }
1336
1337                public TranslateCodeRequest(
1338                                List<IBaseCoding> theCodings,
1339                                String theTargetSystemUrl,
1340                                String theConceptMapUrl,
1341                                String theConceptMapVersion,
1342                                String theSourceValueSetUrl,
1343                                String theTargetValueSetUrl,
1344                                IIdType theResourceId,
1345                                boolean theReverse) {
1346                        myCodings = theCodings;
1347                        myTargetSystemUrl = theTargetSystemUrl;
1348                        myConceptMapUrl = theConceptMapUrl;
1349                        myConceptMapVersion = theConceptMapVersion;
1350                        mySourceValueSetUrl = theSourceValueSetUrl;
1351                        myTargetValueSetUrl = theTargetValueSetUrl;
1352                        myResourceId = theResourceId;
1353                        myReverse = theReverse;
1354                }
1355
1356                @Override
1357                public boolean equals(Object theO) {
1358                        if (this == theO) {
1359                                return true;
1360                        }
1361
1362                        if (theO == null || getClass() != theO.getClass()) {
1363                                return false;
1364                        }
1365
1366                        TranslateCodeRequest that = (TranslateCodeRequest) theO;
1367
1368                        return new EqualsBuilder()
1369                                        .append(myCodings, that.myCodings)
1370                                        .append(myTargetSystemUrl, that.myTargetSystemUrl)
1371                                        .append(myConceptMapUrl, that.myConceptMapUrl)
1372                                        .append(myConceptMapVersion, that.myConceptMapVersion)
1373                                        .append(mySourceValueSetUrl, that.mySourceValueSetUrl)
1374                                        .append(myTargetValueSetUrl, that.myTargetValueSetUrl)
1375                                        .append(myResourceId, that.myResourceId)
1376                                        .append(myReverse, that.myReverse)
1377                                        .isEquals();
1378                }
1379
1380                @Override
1381                public int hashCode() {
1382                        return new HashCodeBuilder(17, 37)
1383                                        .append(myCodings)
1384                                        .append(myTargetSystemUrl)
1385                                        .append(myConceptMapUrl)
1386                                        .append(myConceptMapVersion)
1387                                        .append(mySourceValueSetUrl)
1388                                        .append(myTargetValueSetUrl)
1389                                        .append(myResourceId)
1390                                        .append(myReverse)
1391                                        .toHashCode();
1392                }
1393
1394                public List<IBaseCoding> getCodings() {
1395                        return myCodings;
1396                }
1397
1398                public String getTargetSystemUrl() {
1399                        return myTargetSystemUrl;
1400                }
1401
1402                public String getConceptMapUrl() {
1403                        return myConceptMapUrl;
1404                }
1405
1406                public String getConceptMapVersion() {
1407                        return myConceptMapVersion;
1408                }
1409
1410                public String getSourceValueSetUrl() {
1411                        return mySourceValueSetUrl;
1412                }
1413
1414                public String getTargetValueSetUrl() {
1415                        return myTargetValueSetUrl;
1416                }
1417
1418                public IIdType getResourceId() {
1419                        return myResourceId;
1420                }
1421
1422                public boolean isReverse() {
1423                        return myReverse;
1424                }
1425
1426                @Override
1427                public String toString() {
1428                        return new ToStringBuilder(this)
1429                                        .append("sourceValueSetUrl", mySourceValueSetUrl)
1430                                        .append("targetSystemUrl", myTargetSystemUrl)
1431                                        .append("targetValueSetUrl", myTargetValueSetUrl)
1432                                        .append("reverse", myReverse)
1433                                        .toString();
1434                }
1435        }
1436
1437        /**
1438         * When validating a CodeableConcept containing multiple codings, this method can be used to control whether
1439         * the validator requires all codings in the CodeableConcept to be valid in order to consider the
1440         * CodeableConcept valid.
1441         * <p>
1442         * See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below
1443         * for the behaviour associated with each value.
1444         * </p>
1445         * <p>
1446         *   <ul>
1447         *     <li>If <code>false</code> (default setting) the validation for codings will return a positive result only if
1448         *     ALL codings are valid.</li>
1449         *         <li>If <code>true</code> the validation for codings will return a positive result if ANY codings are valid.
1450         *         </li>
1451         *        </ul>
1452         * </p>
1453         * @return true or false depending on the desired coding validation behaviour.
1454         */
1455        default boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
1456                return false;
1457        }
1458}