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