From 86c2c13e0f41807dda18959a6f571e4b54db894f Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Sun, 24 Nov 2024 12:31:59 -0800
Subject: [PATCH 1/6] 7.6.0 Mergeback (#6494)
* 6323 resource creation deadlock (#6324)
* make map reads concurrent
* change log
* CUstom version number for guaranteed build determinism
* licenses
* Expand translation cache (#6341)
* Expand translation cache
* Add changelog
* Correction to #6341 (#6342)
* Contained bug (#6402)
* Contained bug
* more tests
* changelog, tests, implementation
* code review
* backwards logic
* Fix Questionnaire doc (#6400)
* fix reindex optimizeStorage=ALL_VERSIONS (#6421)
* fixing broken rename of last step of reindex (#6429)
* Fixes for the translation of parameter issues as part of the output for $validate-code operation. (#6438)
* Move the validation providers to the test utilities package such that they can be reused.
* Update IValidationSupport.CodeValidationIssue structure such that it meets the FHIR specification. Update RemoteTerminologyServiceValidationSupport and VersionSpecificWorkerContextWrapper translation for issues.
* New tests for issue translation. Move test class to a different package so that we can add another test class.
* Some simplification in the issue API to simplify building of issues.
* Fix compilation errors.
* Update providers to allow multiple responses and add support for the fetchCodeSystem call through method find.
* Setup the first test for resource validation with remote terminology providers.
* Fix NullPointerException
* avoid calling validateCode for CodeSystem where system is null
* Keep old public API methods (and class name) in IValidationSupport and mark them as deprecated to avoid breaking dependencies.
* Revert local change to debug for duplicate errors.
* Add more test cases for resource validation. Throw exception to signal missing test setup to make it obvious.
* Simplify test setup.
* Add some more javadoc
* Add javadoc for the new test class
* Add more tests
* Address code review comments in IValidationSupport.
* Add changelog
* Change Repository search interface from Map to Multimap (#6445)
Change Map to Multimap to support multiple and clauses.
* licenses
* fix interceptor hooks from requestDetails not getting called for STORAGE_PRECHECK_FOR_CACHED_SEARCH (#6436)
* fix interceptor hooks from requestDetails not getting called for STORAGE_PRECHECK_FOR_CACHED_SEARCH
* added unit tests and updated changelog
* added one more test case
* Add Adapter api (#6450)
Lightweight implementation of the adapter pattern.
* Bulk Import job status not changed after activation - failing test, fix, changelog (#6452)
* Update CR to 3.13.1 (#6433)
* Automated Migration Testing (HAPI-FHIR) - updated test migration scripts for 7_4_0 (#6439)
* Rel 7 6 CVE (#6446)
* Bump commons io
* Bump HS and Lucene, and hibernate
* Jetty bump for CVE
* Resolve CVES
* Bump with mismatch lucene
* Bump velocity template engine
* Revert bom bump
* wip
* Version bump
* move changelog, fix cve
* Bump commons-lang
* Replace imports
* wip
* fix HQL break
* remove dead code
* Fix changelog entry
* Bump org.hl7.fhir.core to 6.4.0 (#6454)
* Bump HAPI version + org.hl7.fhir.core to 6.4.0
* Apply spotless
* Correct a bug with duplicate parser IDs being assigned. (#6456)
* Fix changelog entry
* wip
* compilation problem
* Correct tests
* changelog
* Update hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
* Correct a bug with duplicate parser IDs being assigned - test fixes
* Correct a bug with duplicate parser IDs being assigned - spotless
* Correct a bug with duplicate parser IDs being assigned - added test-utilities dependency to fhir-structures poms
* Correct a bug with duplicate parser IDs being assigned - test fixes
* Contained resources without assigned IDs are now assigned GUIDs - address comments
---------
Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
Co-authored-by: volodymyr
* licenses
* ValueSet expansion fails if Hibernate Search configured to use Lucene - fixed incompatibility between Hibernate Search and Lucene versions (#6468)
* Change the migrator to avoid table locks when adding an index. (#6489)
* version bump
* Updating version to: 7.6.1 post release.
* Bump to 7.7.7.
* deadsapce
* Profile reference param (#6501)
* updated QueryStack to not throw error with _profile as a ReferenceParam
* update QueryStack
* remove warnings
* cleanup cast
* cleanup test
* add changelog
* cleanup imports
* updates per review feedback
* updates per review feedback
---------
Co-authored-by: taha.attari@smilecdr.com
* move changelog
---------
Co-authored-by: JasonRoberts-smile <85363818+JasonRoberts-smile@users.noreply.github.com>
Co-authored-by: James Agnew
Co-authored-by: Brenin Rhodes
Co-authored-by: Emre Dincturk <74370953+mrdnctrk@users.noreply.github.com>
Co-authored-by: TipzCM
Co-authored-by: Martha Mitran
Co-authored-by: Michael Buckley
Co-authored-by: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com>
Co-authored-by: dotasek
Co-authored-by: volodymyr
Co-authored-by: markiantorno
Co-authored-by: Gary Graham
Co-authored-by: Taha
Co-authored-by: taha.attari@smilecdr.com
---
.../context/support/IValidationSupport.java | 321 +++++++++++++++---
.../java/ca/uhn/fhir/parser/BaseParser.java | 46 ++-
.../java/ca/uhn/fhir/parser/JsonParser.java | 15 +-
.../java/ca/uhn/fhir/parser/RDFParser.java | 4 +-
.../java/ca/uhn/fhir/parser/XmlParser.java | 10 +-
.../ca/uhn/fhir/repository/Repository.java | 44 ++-
.../java/ca/uhn/fhir/util/FhirTerser.java | 88 ++---
.../java/ca/uhn/fhir/util/VersionEnum.java | 1 +
.../fhir/util/adapters/AdapterManager.java | 62 ++++
.../uhn/fhir/util/adapters/AdapterUtils.java | 48 +++
.../ca/uhn/fhir/util/adapters/IAdaptable.java | 38 +++
.../fhir/util/adapters/IAdapterFactory.java | 44 +++
.../fhir/util/adapters/IAdapterManager.java | 29 ++
.../uhn/fhir/util/adapters/package-info.java | 39 +++
.../util/adapters/AdapterManagerTest.java | 78 +++++
.../fhir/util/adapters/AdapterUtilsTest.java | 123 +++++++
.../7_6_0/6403-json-parser-bugs-again.yaml | 4 +
.../7_6_0/6403-json-parser-bugs.yaml | 6 +
...ptimize-storage-all-versions-posgtres.yaml | 6 +
...ge-all-versions-for-a-single-resource.yaml | 5 +
.../6422-fixes-remote-terminology-issues.yaml | 7 +
...called-for-precheck-for-cached-search.yaml | 8 +
.../7_6_0/6445-repository-api-multimap.yaml | 4 +
...-status-not-changing-after-activation.yaml | 6 +
...search-lucene-version-incompatibility.yaml | 5 +
...-change-index-add-concurrency-default.yaml | 4 +
.../hapi/fhir/changelog/7_6_0/changes.yaml | 2 +-
.../6502-profile-can-be-reference-param.yaml | 5 +
.../docs/clinical_reasoning/questionnaires.md | 36 +-
.../bulk/imprt/svc/BulkDataImportSvcImpl.java | 7 +
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 9 +-
.../fhir/jpa/dao/BaseHapiFhirSystemDao.java | 2 +-
.../dao/data/IBatch2WorkChunkRepository.java | 2 +-
.../jpa/search/SearchCoordinatorSvcImpl.java | 4 +-
.../fhir/jpa/search/builder/QueryStack.java | 12 +-
.../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 5 +-
.../fhir/jpa/mdm/helper/BaseMdmHelper.java | 3 +
.../jpa/batch2/JpaJobPersistenceImplTest.java | 5 +
.../bulk/imprt/svc/BulkDataImportR4Test.java | 5 +
.../jpa/dao/r4/BaseComboParamsR4Test.java | 3 +
.../r4/FhirResourceDaoR4ContainedTest.java | 3 +
.../dao/r4/FhirResourceDaoR4CreateTest.java | 11 +-
.../r4/FhirResourceDaoR4QueryCountTest.java | 2 +-
...irResourceDaoR4VersionedReferenceTest.java | 4 +-
.../fhir/jpa/dao/r4/FhirSystemDaoR4Test.java | 5 +-
.../provider/r4/ResourceProviderR4Test.java | 2 +
.../uhn/fhir/jpa/reindex/ReindexTaskTest.java | 53 +++
...idateCodeWithRemoteTerminologyR4Test.java} | 167 +++------
.../ValidateWithRemoteTerminologyTest.java | 261 ++++++++++++++
.../encounter/profile-encounter-custom.json | 49 +++
...idateCode-CodeSystem-encounter-status.json | 59 ++++
.../validateCode-CodeSystem-v2-0203.json | 25 ++
.../validateCode-CodeSystem-v3-ActCode.json | 46 +++
...alidateCode-ValueSet-encounter-status.json | 59 ++++
...validateCode-ValueSet-identifier-type.json | 52 +++
...dateCode-ValueSet-v3-ActEncounterCode.json | 59 ++++
.../validateCode-CodeSystem-ICD9CM.json | 48 +++
...ateCode-CodeSystem-observation-status.json | 25 ++
.../validateCode-ValueSet-codes.json | 48 +++
...idateCode-ValueSet-observation-status.json | 25 ++
.../procedure/profile-procedure-slicing.json | 79 +++++
.../procedure/profile-procedure.json | 50 +++
...dateCode-CodeSystem-absent-or-unknown.json | 46 +++
.../validateCode-CodeSystem-event-status.json | 59 ++++
...alidateCode-CodeSystem-snomed-invalid.json | 48 +++
.../validateCode-CodeSystem-snomed-valid.json | 25 ++
...-ValueSet-absent-or-unknown-procedure.json | 59 ++++
.../validateCode-ValueSet-event-status.json | 25 ++
...ValueSet-procedure-code-invalid-slice.json | 48 +++
...eCode-ValueSet-procedure-code-invalid.json | 67 ++++
...ateCode-ValueSet-procedure-code-valid.json | 59 ++++
.../releases/V7_4_0/data/H2_EMBEDDED.sql | 30 ++
.../releases/V7_4_0/data/MSSQL_2012.sql | 30 ++
.../releases/V7_4_0/data/ORACLE_12C.sql | 30 ++
.../releases/V7_4_0/data/POSTGRES_9_4.sql | 30 ++
.../util/CompositeInterceptorBroadcaster.java | 2 +-
.../CompositeInterceptorBroadcasterTest.java | 161 +++++++++
.../fhir/jpa/migrate/tasks/api/Builder.java | 2 +-
.../jobs/reindex/v1/ReindexV1Config.java | 2 +-
.../batch2/model/BatchWorkChunkStatusDTO.java | 1 +
.../config/dstu3/EvaluateOperationConfig.java | 5 +
.../cr/config/r4/EvaluateOperationConfig.java | 5 +
.../fhir/cr/r4/HapiFhirRepositoryR4Test.java | 20 ++
.../fhir/parser/JsonParserDstu2_1Test.java | 13 +-
.../uhn/fhir/parser/XmlParserDstu2_1Test.java | 56 +--
.../uhn/fhir/parser/CustomTypeDstu2Test.java | 14 +-
.../uhn/fhir/parser/JsonParserDstu2Test.java | 7 +-
.../uhn/fhir/parser/XmlParserDstu2Test.java | 57 ++--
.../uhn/fhir/parser/JsonParserDstu3Test.java | 13 +-
.../uhn/fhir/parser/XmlParserDstu3Test.java | 56 +--
.../parser/JsonParserHl7OrgDstu2Test.java | 30 +-
.../fhir/parser/XmlParserHl7OrgDstu2Test.java | 32 +-
.../ca/uhn/fhir/parser/JsonParserR4Test.java | 53 ++-
.../ca/uhn/fhir/util/FhirTerserR4Test.java | 42 ++-
.../ca/uhn/fhir/test/utilities/UuidUtils.java | 47 +++
.../validation/IValidationProviders.java | 123 +++++++
.../validation/IValidationProvidersDstu3.java | 137 ++++++++
.../validation/IValidationProvidersR4.java | 133 +++-----
.../fhir/test/utilities/UuidUtilsTest.java | 29 ++
.../CommonCodeSystemsTerminologyService.java | 2 +-
...oryTerminologyServerValidationSupport.java | 35 +-
...teTerminologyServiceValidationSupport.java | 36 +-
...ownCodeSystemWarningValidationSupport.java | 2 +-
.../support/ValidationSupportUtils.java | 11 +
.../VersionSpecificWorkerContextWrapper.java | 105 ++----
.../hapi/validation/ILookupCodeTest.java | 15 +-
.../IRemoteTerminologyLookupCodeTest.java | 1 +
.../IRemoteTerminologyValidateCodeTest.java | 59 ++++
.../hapi/validation/IValidateCodeTest.java | 146 ++++----
.../hapi/validation/IValidationProviders.java | 39 ---
...rsionSpecificWorkerContextWrapperTest.java | 31 +-
.../FhirInstanceValidatorDstu2Test.java | 8 +-
.../FhirInstanceValidatorDstu3Test.java | 5 +-
.../IValidateCodeProvidersDstu3.java | 159 ---------
...estionnaireResponseValidatorDstu3Test.java | 9 +-
.../RemoteTerminologyLookupCodeDstu3Test.java | 16 +-
...gyLookupCodeWithResponseFileDstu3Test.java | 22 +-
...emoteTerminologyValidateCodeDstu3Test.java | 52 +--
.../fhir/r4/utils/FhirPathEngineR4Test.java | 4 +
.../FhirInstanceValidatorR4Test.java | 4 +-
.../RemoteTerminologyLookupCodeR4Test.java | 21 +-
...ologyLookupCodeWithResponseFileR4Test.java | 22 +-
.../RemoteTerminologyValidateCodeR4Test.java | 101 +++---
.../FhirInstanceValidatorR4BTest.java | 4 +-
.../FhirInstanceValidatorR5Test.java | 5 +-
...nOutcome-ValueSet-custom-issue-detail.json | 22 ++
.../ca/uhn/fhir/tinder/Configuration.java | 2 +-
.../tinder/TinderGenericSingleFileMojo.java | 2 +-
.../fhir/tinder/TinderJpaRestServerMojo.java | 2 +-
.../fhir/tinder/ant/TinderGeneratorTask.java | 2 +-
.../ca/uhn/fhir/tinder/model/BaseElement.java | 4 +-
.../fhir/tinder/model/SearchParameter.java | 2 +-
.../tinder/parser/BaseStructureParser.java | 6 +-
.../parser/ResourceGeneratorUsingModel.java | 2 +-
pom.xml | 18 +-
135 files changed, 3801 insertions(+), 1048 deletions(-)
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java
create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java
create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6502-profile-can-be-reference-param.yaml
rename hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/{provider/r4/ValidateCodeOperationWithRemoteTerminologyR4Test.java => validation/ValidateCodeWithRemoteTerminologyR4Test.java} (59%)
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/validation/ValidateWithRemoteTerminologyTest.java
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/profile-encounter-custom.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-encounter-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v2-0203.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-CodeSystem-v3-ActCode.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-encounter-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-identifier-type.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/encounter/validateCode-ValueSet-v3-ActEncounterCode.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-ICD9CM.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-CodeSystem-observation-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-codes.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/observation/validateCode-ValueSet-observation-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure-slicing.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/profile-procedure.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-absent-or-unknown.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-event-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-invalid.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-CodeSystem-snomed-valid.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-absent-or-unknown-procedure.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-event-status.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid-slice.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-invalid.json
create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/resources/validation/procedure/validateCode-ValueSet-procedure-code-valid.json
create mode 100644 hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/util/CompositeInterceptorBroadcasterTest.java
create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/UuidUtils.java
create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProviders.java
create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersDstu3.java
rename hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/IValidateCodeProvidersR4.java => hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/validation/IValidationProvidersR4.java (55%)
create mode 100644 hapi-fhir-test-utilities/src/test/java/ca/uhn/fhir/test/utilities/UuidUtilsTest.java
delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/IValidationProviders.java
delete mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/IValidateCodeProvidersDstu3.java
create mode 100644 hapi-fhir-validation/src/test/resources/terminology/OperationOutcome-ValueSet-custom-issue-detail.json
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java
index 71d561c1db3..fdbc4ca31d7 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java
@@ -440,74 +440,259 @@ public interface IValidationSupport {
return "Unknown " + getFhirContext().getVersion().getVersion() + " Validation Support";
}
+ /**
+ * Defines codes in system http://hl7.org/fhir/issue-severity.
+ */
+ /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
enum IssueSeverity {
/**
* The issue caused the action to fail, and no further checking could be performed.
*/
- FATAL,
+ FATAL("fatal"),
/**
* The issue is sufficiently important to cause the action to fail.
*/
- ERROR,
+ ERROR("error"),
/**
* 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.
*/
- WARNING,
+ WARNING("warning"),
/**
* The issue has no relation to the degree of success of the action.
*/
- INFORMATION
+ INFORMATION("information"),
+ /**
+ * The operation was successful.
+ */
+ SUCCESS("success");
+ // the spec for OperationOutcome mentions that a code from http://hl7.org/fhir/issue-severity is required
+
+ private final String myCode;
+
+ IssueSeverity(String theCode) {
+ myCode = theCode;
+ }
+ /**
+ * Provide mapping to a code in system http://hl7.org/fhir/issue-severity.
+ * @return the code
+ */
+ public String getCode() {
+ return myCode;
+ }
+ /**
+ * Creates a {@link IssueSeverity} object from the given code.
+ * @return the {@link IssueSeverity}
+ */
+ public static IssueSeverity fromCode(String theCode) {
+ switch (theCode) {
+ case "fatal":
+ return FATAL;
+ case "error":
+ return ERROR;
+ case "warning":
+ return WARNING;
+ case "information":
+ return INFORMATION;
+ case "success":
+ return SUCCESS;
+ default:
+ return null;
+ }
+ }
}
- enum CodeValidationIssueCode {
- NOT_FOUND,
- CODE_INVALID,
- INVALID,
- OTHER
- }
+ /**
+ * Defines codes in system http://hl7.org/fhir/issue-type.
+ * The binding is enforced as a part of validation logic in the FHIR Core Validation library where an exception is thrown.
+ * Only a sub-set of these codes are defined as constants because they relate to validation,
+ * If there are additional ones that come up, for Remote Terminology they are currently supported via
+ * {@link IValidationSupport.CodeValidationIssue#CodeValidationIssue(String, IssueSeverity, String)}
+ * while for internal validators, more constants can be added to make things easier and consistent.
+ * This maps to resource OperationOutcome.issue.code.
+ */
+ /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
+ class CodeValidationIssueCode {
+ public static final CodeValidationIssueCode NOT_FOUND = new CodeValidationIssueCode("not-found");
+ public static final CodeValidationIssueCode CODE_INVALID = new CodeValidationIssueCode("code-invalid");
+ public static final CodeValidationIssueCode INVALID = new CodeValidationIssueCode("invalid");
- enum CodeValidationIssueCoding {
- VS_INVALID,
- NOT_FOUND,
- NOT_IN_VS,
+ private final String myCode;
- INVALID_CODE,
- INVALID_DISPLAY,
- OTHER
- }
-
- class CodeValidationIssue {
-
- private final String myMessage;
- private final IssueSeverity mySeverity;
- private final CodeValidationIssueCode myCode;
- private final CodeValidationIssueCoding myCoding;
-
- public CodeValidationIssue(
- String theMessage,
- IssueSeverity mySeverity,
- CodeValidationIssueCode theCode,
- CodeValidationIssueCoding theCoding) {
- this.myMessage = theMessage;
- this.mySeverity = mySeverity;
- this.myCode = theCode;
- this.myCoding = theCoding;
+ // this is intentionally not exposed
+ CodeValidationIssueCode(String theCode) {
+ myCode = theCode;
}
+ /**
+ * Retrieve the corresponding code from system http://hl7.org/fhir/issue-type.
+ * @return the code
+ */
+ public String getCode() {
+ return myCode;
+ }
+ }
+
+ /**
+ * Holds information about the details of a {@link CodeValidationIssue}.
+ * This maps to resource OperationOutcome.issue.details.
+ */
+ /* this enum would not be needed if we design/refactor to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult */
+ class CodeValidationIssueDetails {
+ private final String myText;
+ private List myCodings;
+
+ public CodeValidationIssueDetails(String theText) {
+ myText = theText;
+ }
+
+ // intentionally not exposed
+ void addCoding(CodeValidationIssueCoding theCoding) {
+ getCodings().add(theCoding);
+ }
+
+ public CodeValidationIssueDetails addCoding(String theSystem, String theCode) {
+ if (myCodings == null) {
+ myCodings = new ArrayList<>();
+ }
+ myCodings.add(new CodeValidationIssueCoding(theSystem, theCode));
+ return this;
+ }
+
+ public String getText() {
+ return myText;
+ }
+
+ public List getCodings() {
+ if (myCodings == null) {
+ myCodings = new ArrayList<>();
+ }
+ return myCodings;
+ }
+ }
+
+ /**
+ * Defines codes that can be part of the details of an issue.
+ * There are some constants available (pre-defined) for codes for system http://hl7.org/fhir/tools/CodeSystem/tx-issue-type.
+ * This maps to resource OperationOutcome.issue.details.coding[0].code.
+ */
+ class CodeValidationIssueCoding {
+ public static String TX_ISSUE_SYSTEM = "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type";
+ public static CodeValidationIssueCoding VS_INVALID =
+ new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-invalid");
+ public static final CodeValidationIssueCoding NOT_FOUND =
+ new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-found");
+ public static final CodeValidationIssueCoding NOT_IN_VS =
+ new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "not-in-vs");
+ public static final CodeValidationIssueCoding INVALID_CODE =
+ new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "invalid-code");
+ public static final CodeValidationIssueCoding INVALID_DISPLAY =
+ new CodeValidationIssueCoding(TX_ISSUE_SYSTEM, "vs-display");
+ private final String mySystem, myCode;
+
+ // this is intentionally not exposed
+ CodeValidationIssueCoding(String theSystem, String theCode) {
+ mySystem = theSystem;
+ myCode = theCode;
+ }
+
+ /**
+ * Retrieve the corresponding code for the details of a validation issue.
+ * @return the code
+ */
+ public String getCode() {
+ return myCode;
+ }
+
+ /**
+ * Retrieve the system for the details of a validation issue.
+ * @return the system
+ */
+ public String getSystem() {
+ return mySystem;
+ }
+ }
+
+ /**
+ * This is a hapi-fhir internal version agnostic object holding information about a validation issue.
+ * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult instead.
+ */
+ class CodeValidationIssue {
+ private final String myDiagnostics;
+ private final IssueSeverity mySeverity;
+ private final CodeValidationIssueCode myCode;
+ private CodeValidationIssueDetails myDetails;
+
+ public CodeValidationIssue(
+ String theDiagnostics, IssueSeverity theSeverity, CodeValidationIssueCode theTypeCode) {
+ this(theDiagnostics, theSeverity, theTypeCode, null);
+ }
+
+ public CodeValidationIssue(String theDiagnostics, IssueSeverity theSeverity, String theTypeCode) {
+ this(theDiagnostics, theSeverity, new CodeValidationIssueCode(theTypeCode), null);
+ }
+
+ public CodeValidationIssue(
+ String theDiagnostics,
+ IssueSeverity theSeverity,
+ CodeValidationIssueCode theType,
+ CodeValidationIssueCoding theDetailsCoding) {
+ myDiagnostics = theDiagnostics;
+ mySeverity = theSeverity;
+ myCode = theType;
+ // reuse the diagnostics message as a detail text message
+ myDetails = new CodeValidationIssueDetails(theDiagnostics);
+ myDetails.addCoding(theDetailsCoding);
+ }
+
+ /**
+ * @deprecated Please use {@link #getDiagnostics()} instead.
+ */
+ @Deprecated(since = "7.4.6")
public String getMessage() {
- return myMessage;
+ return getDiagnostics();
+ }
+
+ public String getDiagnostics() {
+ return myDiagnostics;
}
public IssueSeverity getSeverity() {
return mySeverity;
}
+ /**
+ * @deprecated Please use {@link #getType()} instead.
+ */
+ @Deprecated(since = "7.4.6")
public CodeValidationIssueCode getCode() {
+ return getType();
+ }
+
+ public CodeValidationIssueCode getType() {
return myCode;
}
+ /**
+ * @deprecated Please use {@link #getDetails()} instead. That has support for multiple codings.
+ */
+ @Deprecated(since = "7.4.6")
public CodeValidationIssueCoding getCoding() {
- return myCoding;
+ return myDetails != null
+ ? myDetails.getCodings().stream().findFirst().orElse(null)
+ : null;
+ }
+
+ public void setDetails(CodeValidationIssueDetails theDetails) {
+ this.myDetails = theDetails;
+ }
+
+ public CodeValidationIssueDetails getDetails() {
+ return myDetails;
+ }
+
+ public boolean hasIssueDetailCode(@Nonnull String theCode) {
+ // this method is system agnostic at the moment but it can be restricted if needed
+ return myDetails.getCodings().stream().anyMatch(coding -> theCode.equals(coding.getCode()));
}
}
@@ -671,6 +856,10 @@ public interface IValidationSupport {
}
}
+ /**
+ * This is a hapi-fhir internal version agnostic object holding information about the validation result.
+ * An alternative (which requires significant refactoring) would be to use org.hl7.fhir.r5.terminologies.utilities.ValidationResult.
+ */
class CodeValidationResult {
public static final String SOURCE_DETAILS = "sourceDetails";
public static final String RESULT = "result";
@@ -686,7 +875,7 @@ public interface IValidationSupport {
private String myDisplay;
private String mySourceDetails;
- private List myCodeValidationIssues;
+ private List myIssues;
public CodeValidationResult() {
super();
@@ -771,20 +960,45 @@ public interface IValidationSupport {
return this;
}
+ /**
+ * @deprecated Please use method {@link #getIssues()} instead.
+ */
+ @Deprecated(since = "7.4.6")
public List getCodeValidationIssues() {
- if (myCodeValidationIssues == null) {
- myCodeValidationIssues = new ArrayList<>();
- }
- return myCodeValidationIssues;
+ return getIssues();
}
+ /**
+ * @deprecated Please use method {@link #setIssues(List)} instead.
+ */
+ @Deprecated(since = "7.4.6")
public CodeValidationResult setCodeValidationIssues(List theCodeValidationIssues) {
- myCodeValidationIssues = new ArrayList<>(theCodeValidationIssues);
+ return setIssues(theCodeValidationIssues);
+ }
+
+ /**
+ * @deprecated Please use method {@link #addIssue(CodeValidationIssue)} instead.
+ */
+ @Deprecated(since = "7.4.6")
+ public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
+ getCodeValidationIssues().add(theCodeValidationIssue);
return this;
}
- public CodeValidationResult addCodeValidationIssue(CodeValidationIssue theCodeValidationIssue) {
- getCodeValidationIssues().add(theCodeValidationIssue);
+ public List getIssues() {
+ if (myIssues == null) {
+ myIssues = new ArrayList<>();
+ }
+ return myIssues;
+ }
+
+ public CodeValidationResult setIssues(List theIssues) {
+ myIssues = new ArrayList<>(theIssues);
+ return this;
+ }
+
+ public CodeValidationResult addIssue(CodeValidationIssue theCodeValidationIssue) {
+ getIssues().add(theCodeValidationIssue);
return this;
}
@@ -811,17 +1025,19 @@ public interface IValidationSupport {
public String getSeverityCode() {
String retVal = null;
if (getSeverity() != null) {
- retVal = getSeverity().name().toLowerCase();
+ retVal = getSeverity().getCode();
}
return retVal;
}
/**
- * Sets an issue severity as a string code. Value must be the name of
- * one of the enum values in {@link IssueSeverity}. Value is case-insensitive.
+ * Sets an issue severity using a severity code. Please use method {@link #setSeverity(IssueSeverity)} instead.
+ * @param theSeverityCode the code
+ * @return the current {@link CodeValidationResult} instance
*/
- public CodeValidationResult setSeverityCode(@Nonnull String theIssueSeverity) {
- setSeverity(IssueSeverity.valueOf(theIssueSeverity.toUpperCase()));
+ @Deprecated(since = "7.4.6")
+ public CodeValidationResult setSeverityCode(@Nonnull String theSeverityCode) {
+ setSeverity(IssueSeverity.fromCode(theSeverityCode));
return this;
}
@@ -838,6 +1054,11 @@ public interface IValidationSupport {
if (isNotBlank(getSourceDetails())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, SOURCE_DETAILS, getSourceDetails());
}
+ /*
+ should translate issues as well, except that is version specific code, so it requires more refactoring
+ or replace the current class with org.hl7.fhir.r5.terminologies.utilities.ValidationResult
+ @see VersionSpecificWorkerContextWrapper#getIssuesForCodeValidation
+ */
return retVal;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
index f0559373e0a..ce932333840 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java
@@ -105,7 +105,6 @@ public abstract class BaseParser implements IParser {
private static final Set notEncodeForContainedResource =
new HashSet<>(Arrays.asList("security", "versionId", "lastUpdated"));
- private FhirTerser.ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private final FhirContext myContext;
private Collection myDontEncodeElements;
@@ -183,12 +182,15 @@ public abstract class BaseParser implements IParser {
}
private String determineReferenceText(
- IBaseReference theRef, CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
+ IBaseReference theRef,
+ CompositeChildElement theCompositeChildElement,
+ IBaseResource theResource,
+ EncodeContext theContext) {
IIdType ref = theRef.getReferenceElement();
if (isBlank(ref.getIdPart())) {
String reference = ref.getValue();
if (theRef.getResource() != null) {
- IIdType containedId = getContainedResources().getResourceId(theRef.getResource());
+ IIdType containedId = theContext.getContainedResources().getResourceId(theRef.getResource());
if (containedId != null && !containedId.isEmpty()) {
if (containedId.isLocal()) {
reference = containedId.getValue();
@@ -262,7 +264,8 @@ public abstract class BaseParser implements IParser {
@Override
public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter)
throws IOException, DataFormatException {
- EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
+ EncodeContext encodeContext =
+ new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
encodeResourceToWriter(theResource, theWriter, encodeContext);
}
@@ -285,7 +288,8 @@ public abstract class BaseParser implements IParser {
} else if (theElement instanceof IPrimitiveType) {
theWriter.write(((IPrimitiveType>) theElement).getValueAsString());
} else {
- EncodeContext encodeContext = new EncodeContext(this, myContext.getParserOptions());
+ EncodeContext encodeContext =
+ new EncodeContext(this, myContext.getParserOptions(), new FhirTerser.ContainedResources());
encodeToWriter(theElement, theWriter, encodeContext);
}
}
@@ -404,10 +408,6 @@ public abstract class BaseParser implements IParser {
return elementId;
}
- FhirTerser.ContainedResources getContainedResources() {
- return myContainedResources;
- }
-
@Override
public Set getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
@@ -539,10 +539,11 @@ public abstract class BaseParser implements IParser {
return mySuppressNarratives;
}
- protected boolean isChildContained(BaseRuntimeElementDefinition> childDef, boolean theIncludedResource) {
+ protected boolean isChildContained(
+ BaseRuntimeElementDefinition> childDef, boolean theIncludedResource, EncodeContext theContext) {
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES
|| childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST)
- && getContainedResources().isEmpty() == false
+ && theContext.getContainedResources().isEmpty() == false
&& theIncludedResource == false;
}
@@ -788,7 +789,8 @@ public abstract class BaseParser implements IParser {
*/
if (next instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) next;
- String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource);
+ String refText =
+ determineReferenceText(nextRef, theCompositeChildElement, theResource, theEncodeContext);
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
if (retVal == theValues) {
@@ -980,7 +982,7 @@ public abstract class BaseParser implements IParser {
return true;
}
- protected void containResourcesInReferences(IBaseResource theResource) {
+ protected void containResourcesInReferences(IBaseResource theResource, EncodeContext theContext) {
/*
* If a UUID is present in Bundle.entry.fullUrl but no value is present
@@ -1003,7 +1005,7 @@ public abstract class BaseParser implements IParser {
}
}
- myContainedResources = getContext().newTerser().containResources(theResource);
+ theContext.setContainedResources(getContext().newTerser().containResources(theResource));
}
static class ChildNameAndDef {
@@ -1034,8 +1036,12 @@ public abstract class BaseParser implements IParser {
private final List myEncodeElementPaths;
private final Set myEncodeElementsAppliesToResourceTypes;
private final List myDontEncodeElementPaths;
+ private FhirTerser.ContainedResources myContainedResources;
- public EncodeContext(BaseParser theParser, ParserOptions theParserOptions) {
+ public EncodeContext(
+ BaseParser theParser,
+ ParserOptions theParserOptions,
+ FhirTerser.ContainedResources theContainedResources) {
Collection encodeElements = theParser.myEncodeElements;
Collection dontEncodeElements = theParser.myDontEncodeElements;
if (isSummaryMode()) {
@@ -1058,6 +1064,8 @@ public abstract class BaseParser implements IParser {
dontEncodeElements.stream().map(EncodeContextPath::new).collect(Collectors.toList());
}
+ myContainedResources = theContainedResources;
+
myEncodeElementsAppliesToResourceTypes =
ParserUtil.determineApplicableResourceTypesForTerserPaths(myEncodeElementPaths);
}
@@ -1065,6 +1073,14 @@ public abstract class BaseParser implements IParser {
private Map> getCompositeChildrenCache() {
return myCompositeChildrenCache;
}
+
+ public FhirTerser.ContainedResources getContainedResources() {
+ return myContainedResources;
+ }
+
+ public void setContainedResources(FhirTerser.ContainedResources theContainedResources) {
+ myContainedResources = theContainedResources;
+ }
}
protected class CompositeChildElement {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 0b25304ee5f..5731742ac1f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -54,6 +54,7 @@ import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.parser.json.jackson.JacksonStructure;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.util.ElementUtil;
+import ca.uhn.fhir.util.FhirTerser;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.text.WordUtils;
@@ -386,12 +387,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
case CONTAINED_RESOURCE_LIST:
case CONTAINED_RESOURCES: {
- List containedResources = getContainedResources().getContainedResources();
+ List containedResources =
+ theEncodeContext.getContainedResources().getContainedResources();
if (containedResources.size() > 0) {
beginArray(theEventWriter, theChildName);
for (IBaseResource next : containedResources) {
- IIdType resourceId = getContainedResources().getResourceId(next);
+ IIdType resourceId =
+ theEncodeContext.getContainedResources().getResourceId(next);
String value = resourceId.getValue();
encodeResourceToJsonStreamWriter(
theResDef,
@@ -554,7 +557,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
if (nextValue == null || nextValue.isEmpty()) {
if (nextValue instanceof BaseContainedDt) {
- if (theContainedResource || getContainedResources().isEmpty()) {
+ if (theContainedResource
+ || theEncodeContext.getContainedResources().isEmpty()) {
continue;
}
} else {
@@ -838,7 +842,8 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
+ theResource.getStructureFhirVersionEnum());
}
- EncodeContext encodeContext = new EncodeContext(this, getContext().getParserOptions());
+ EncodeContext encodeContext =
+ new EncodeContext(this, getContext().getParserOptions(), new FhirTerser.ContainedResources());
String resourceName = getContext().getResourceType(theResource);
encodeContext.pushPath(resourceName, true);
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
@@ -894,7 +899,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
if (!theContainedResource) {
- containResourcesInReferences(theResource);
+ containResourcesInReferences(theResource, theEncodeContext);
}
RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
index b75ee9c897f..18572824fee 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
@@ -191,7 +191,7 @@ public class RDFParser extends BaseParser {
}
if (!containedResource) {
- containResourcesInReferences(resource);
+ containResourcesInReferences(resource, encodeContext);
}
if (!(resource instanceof IAnyResource)) {
@@ -354,7 +354,7 @@ public class RDFParser extends BaseParser {
try {
if (element == null || element.isEmpty()) {
- if (!isChildContained(childDef, includedResource)) {
+ if (!isChildContained(childDef, includedResource, theEncodeContext)) {
return rdfModel;
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
index 7a5aaa021bf..71b83de8d8e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
@@ -295,7 +295,7 @@ public class XmlParser extends BaseParser {
try {
if (theElement == null || theElement.isEmpty()) {
- if (isChildContained(childDef, theIncludedResource)) {
+ if (isChildContained(childDef, theIncludedResource, theEncodeContext)) {
// We still want to go in..
} else {
return;
@@ -359,8 +359,10 @@ public class XmlParser extends BaseParser {
* theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue()));
* theEventWriter.writeEndElement(); }
*/
- for (IBaseResource next : getContainedResources().getContainedResources()) {
- IIdType resourceId = getContainedResources().getResourceId(next);
+ for (IBaseResource next :
+ theEncodeContext.getContainedResources().getContainedResources()) {
+ IIdType resourceId =
+ theEncodeContext.getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
String value = resourceId.getValue();
encodeResourceToXmlStreamWriter(
@@ -682,7 +684,7 @@ public class XmlParser extends BaseParser {
}
if (!theContainedResource) {
- containResourcesInReferences(theResource);
+ containResourcesInReferences(theResource, theEncodeContext);
}
theEventWriter.writeStartElement(resDef.getName());
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java
index e859a9ae569..c272656f47a 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/repository/Repository.java
@@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import com.google.common.annotations.Beta;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseParameters;
@@ -231,6 +233,23 @@ public interface Repository {
// Querying starts here
+ /**
+ * Searches this repository
+ *
+ * @see FHIR search
+ *
+ * @param a Bundle type
+ * @param a Resource type
+ * @param bundleType the class of the Bundle type to return
+ * @param resourceType the class of the Resource type to search
+ * @param searchParameters the searchParameters for this search
+ * @return a Bundle with the results of the search
+ */
+ default B search(
+ Class bundleType, Class resourceType, Multimap> searchParameters) {
+ return this.search(bundleType, resourceType, searchParameters, Collections.emptyMap());
+ }
+
/**
* Searches this repository
*
@@ -264,9 +283,32 @@ public interface Repository {
B search(
Class bundleType,
Class resourceType,
- Map> searchParameters,
+ Multimap> searchParameters,
Map headers);
+ /**
+ * Searches this repository
+ *
+ * @see FHIR search
+ *
+ * @param a Bundle type
+ * @param a Resource type
+ * @param bundleType the class of the Bundle type to return
+ * @param resourceType the class of the Resource type to search
+ * @param searchParameters the searchParameters for this search
+ * @param headers headers for this request, typically key-value pairs of HTTP headers
+ * @return a Bundle with the results of the search
+ */
+ default B search(
+ Class bundleType,
+ Class resourceType,
+ Map> searchParameters,
+ Map headers) {
+ ArrayListMultimap> multimap = ArrayListMultimap.create();
+ searchParameters.forEach(multimap::put);
+ return this.search(bundleType, resourceType, multimap, headers);
+ }
+
// Paging starts here
/**
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
index 6eab1ce9ed3..eebb07b63c3 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
@@ -38,7 +38,6 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
-import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.common.collect.Lists;
@@ -61,6 +60,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -70,6 +70,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -77,16 +78,28 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import static org.apache.commons.lang3.StringUtils.substring;
public class FhirTerser {
private static final Pattern COMPARTMENT_MATCHER_PATH =
Pattern.compile("([a-zA-Z.]+)\\.where\\(resolve\\(\\) is ([a-zA-Z]+)\\)");
+
private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED =
FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED";
+
private final FhirContext myContext;
+ /**
+ * This comparator sorts IBaseReferences, and places any that are missing an ID at the end. Those with an ID go to the front.
+ */
+ private static final Comparator REFERENCES_WITH_IDS_FIRST =
+ Comparator.nullsLast(Comparator.comparing(ref -> {
+ if (ref.getResource() == null) return true;
+ if (ref.getResource().getIdElement() == null) return true;
+ if (ref.getResource().getIdElement().getValue() == null) return true;
+ return false;
+ }));
+
public FhirTerser(FhirContext theContext) {
super();
myContext = theContext;
@@ -1418,6 +1431,13 @@ public class FhirTerser {
private void containResourcesForEncoding(
ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) {
List allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class);
+
+ // Note that we process all contained resources that have arrived here with an ID contained resources first, so
+ // that we don't accidentally auto-assign an ID
+ // which may collide with a resource we have yet to process.
+ // See: https://github.com/hapifhir/hapi-fhir/issues/6403
+ allReferences.sort(REFERENCES_WITH_IDS_FIRST);
+
for (IBaseReference next : allReferences) {
IBaseResource resource = next.getResource();
if (resource == null && next.getReferenceElement().isLocal()) {
@@ -1437,11 +1457,11 @@ public class FhirTerser {
IBaseResource resource = next.getResource();
if (resource != null) {
if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) {
- if (theContained.getResourceId(resource) != null) {
- // Prevent infinite recursion if there are circular loops in the contained resources
+
+ IIdType id = theContained.addContained(resource);
+ if (id == null) {
continue;
}
- IIdType id = theContained.addContained(resource);
if (theModifyResource) {
getContainedResourceList(theResource).add(resource);
next.setReference(id.getValue());
@@ -1768,8 +1788,6 @@ public class FhirTerser {
}
public static class ContainedResources {
- private long myNextContainedId = 1;
-
private List myResourceList;
private IdentityHashMap myResourceToIdMap;
private Map myExistingIdToContainedResourceMap;
@@ -1782,6 +1800,11 @@ public class FhirTerser {
}
public IIdType addContained(IBaseResource theResource) {
+ if (this.getResourceId(theResource) != null) {
+ // Prevent infinite recursion if there are circular loops in the contained resources
+ return null;
+ }
+
IIdType existing = getResourceToIdMap().get(theResource);
if (existing != null) {
return existing;
@@ -1789,16 +1812,7 @@ public class FhirTerser {
IIdType newId = theResource.getIdElement();
if (isBlank(newId.getValue())) {
- newId.setValue("#" + myNextContainedId++);
- } else {
- // Avoid auto-assigned contained IDs colliding with pre-existing ones
- String idPart = newId.getValue();
- if (substring(idPart, 0, 1).equals("#")) {
- idPart = idPart.substring(1);
- if (StringUtils.isNumeric(idPart)) {
- myNextContainedId = Long.parseLong(idPart) + 1;
- }
- }
+ newId.setValue("#" + UUID.randomUUID());
}
getResourceToIdMap().put(theResource, newId);
@@ -1862,45 +1876,5 @@ public class FhirTerser {
public boolean hasExistingIdToContainedResource() {
return myExistingIdToContainedResourceMap != null;
}
-
- public void assignIdsToContainedResources() {
-
- if (!getContainedResources().isEmpty()) {
-
- /*
- * The idea with the code block below:
- *
- * We want to preserve any IDs that were user-assigned, so that if it's really
- * important to someone that their contained resource have the ID of #FOO
- * or #1 we will keep that.
- *
- * For any contained resources where no ID was assigned by the user, we
- * want to manually create an ID but make sure we don't reuse an existing ID.
- */
-
- Set ids = new HashSet<>();
-
- // Gather any user assigned IDs
- for (IBaseResource nextResource : getContainedResources()) {
- if (getResourceToIdMap().get(nextResource) != null) {
- ids.add(getResourceToIdMap().get(nextResource).getValue());
- }
- }
-
- // Automatically assign IDs to the rest
- for (IBaseResource nextResource : getContainedResources()) {
-
- while (getResourceToIdMap().get(nextResource) == null) {
- String nextCandidate = "#" + myNextContainedId;
- myNextContainedId++;
- if (!ids.add(nextCandidate)) {
- continue;
- }
-
- getResourceToIdMap().put(nextResource, new IdDt(nextCandidate));
- }
- }
- }
- }
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
index 8d4867594cf..0f89ed04d07 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java
@@ -170,6 +170,7 @@ public enum VersionEnum {
V7_5_0,
V7_6_0,
+ V7_6_1,
V7_7_0,
V7_8_0;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java
new file mode 100644
index 00000000000..93a7b054a39
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterManager.java
@@ -0,0 +1,62 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.util.adapters;
+
+import jakarta.annotation.Nonnull;
+
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class AdapterManager implements IAdapterManager {
+ public static final AdapterManager INSTANCE = new AdapterManager();
+
+ Set myAdapterFactories = new HashSet<>();
+
+ /**
+ * Hidden to force shared use of the public INSTANCE.
+ */
+ AdapterManager() {}
+
+ public @Nonnull Optional getAdapter(Object theObject, Class theTargetType) {
+ // todo this can be sped up with a cache of type->Factory.
+ return myAdapterFactories.stream()
+ .filter(nextFactory -> nextFactory.getAdapters().stream().anyMatch(theTargetType::isAssignableFrom))
+ .flatMap(nextFactory -> {
+ var adapter = nextFactory.getAdapter(theObject, theTargetType);
+ // can't use Optional.stream() because of our Android target is API level 26/JDK 8.
+ if (adapter.isPresent()) {
+ return Stream.of(adapter.get());
+ } else {
+ return Stream.empty();
+ }
+ })
+ .findFirst();
+ }
+
+ public void registerFactory(@Nonnull IAdapterFactory theFactory) {
+ myAdapterFactories.add(theFactory);
+ }
+
+ public void unregisterFactory(@Nonnull IAdapterFactory theFactory) {
+ myAdapterFactories.remove(theFactory);
+ }
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java
new file mode 100644
index 00000000000..369b7ab324e
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/AdapterUtils.java
@@ -0,0 +1,48 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.util.adapters;
+
+import java.util.Optional;
+
+public class AdapterUtils {
+
+ /**
+ * Main entry point for adapter calls.
+ * Implements three conversions: cast to the target type, use IAdaptable if present, or lastly try the AdapterManager.INSTANCE.
+ * @param theObject the object to be adapted
+ * @param theTargetType the type of the adapter requested
+ */
+ static Optional adapt(Object theObject, Class theTargetType) {
+ if (theTargetType.isInstance(theObject)) {
+ //noinspection unchecked
+ return Optional.of((T) theObject);
+ }
+
+ if (theObject instanceof IAdaptable) {
+ IAdaptable adaptable = (IAdaptable) theObject;
+ var adapted = adaptable.getAdapter(theTargetType);
+ if (adapted.isPresent()) {
+ return adapted;
+ }
+ }
+
+ return AdapterManager.INSTANCE.getAdapter(theObject, theTargetType);
+ }
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java
new file mode 100644
index 00000000000..4310c294fba
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdaptable.java
@@ -0,0 +1,38 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.util.adapters;
+
+import jakarta.annotation.Nonnull;
+
+import java.util.Optional;
+
+/**
+ * Generic version of Eclipse IAdaptable interface.
+ */
+public interface IAdaptable {
+ /**
+ * Get an adapter of requested type.
+ * @param theTargetType the desired type of the adapter
+ * @return an adapter of theTargetType if possible, or empty.
+ */
+ default @Nonnull Optional getAdapter(@Nonnull Class theTargetType) {
+ return AdapterUtils.adapt(this, theTargetType);
+ }
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java
new file mode 100644
index 00000000000..f4aa88edde5
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterFactory.java
@@ -0,0 +1,44 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.util.adapters;
+
+import java.util.Collection;
+import java.util.Optional;
+
+/**
+ * Interface for external service that builds adaptors for targets.
+ */
+public interface IAdapterFactory {
+ /**
+ * Build an adaptor for the target.
+ * May return empty() even if the target type is listed in getAdapters() when
+ * the factory fails to convert a particular instance.
+ *
+ * @param theObject the object to be adapted.
+ * @param theAdapterType the target type
+ * @return the adapter, if possible.
+ */
+ Optional getAdapter(Object theObject, Class theAdapterType);
+
+ /**
+ * @return the collection of adapter target types handled by this factory.
+ */
+ Collection> getAdapters();
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java
new file mode 100644
index 00000000000..d199267f927
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/IAdapterManager.java
@@ -0,0 +1,29 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.util.adapters;
+
+import java.util.Optional;
+
+/**
+ * Get an adaptor
+ */
+public interface IAdapterManager {
+ Optional getAdapter(Object theTarget, Class theAdapter);
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java
new file mode 100644
index 00000000000..c053c1b0248
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/adapters/package-info.java
@@ -0,0 +1,39 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+/**
+ * Implements the Adapter pattern to allow external classes to extend/adapt existing classes.
+ * Useful for extending interfaces that are closed to modification, or restricted for classpath reasons.
+ *
+ * For clients, the main entry point is {@link ca.uhn.fhir.util.adapters.AdapterUtils#adapt(java.lang.Object, java.lang.Class)}
+ * which will attempt to cast to the target type, or build an adapter of the target type.
+ *
+ *
+ * For implementors, you can support adaptation via two mechanisms:
+ *
+ *
by implementing {@link ca.uhn.fhir.util.adapters.IAdaptable} directly on a class to provide supported adapters,
+ *
or when the class is closed to direct modification, you can implement
+ * an instance of {@link ca.uhn.fhir.util.adapters.IAdapterFactory} and register
+ * it with the public {@link ca.uhn.fhir.util.adapters.AdapterManager#INSTANCE}.
+ *
+ * The AdapterUtils.adapt() supports both of these.
+ *
+ * Inspired by the Eclipse runtime.
+ */
+package ca.uhn.fhir.util.adapters;
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java
new file mode 100644
index 00000000000..ee621533587
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterManagerTest.java
@@ -0,0 +1,78 @@
+package ca.uhn.fhir.util.adapters;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AdapterManagerTest {
+ AdapterManager myAdapterManager = new AdapterManager();
+
+ @AfterAll
+ static void tearDown() {
+ assertThat(AdapterManager.INSTANCE.myAdapterFactories)
+ .withFailMessage("Don't dirty the public instance").isEmpty();
+ }
+
+ @Test
+ void testRegisterFactory_providesAdapter() {
+ // given
+ myAdapterManager.registerFactory(new StringToIntFactory());
+
+ // when
+ var result = myAdapterManager.getAdapter("22", Integer.class);
+
+ // then
+ assertThat(result).contains(22);
+ }
+
+ @Test
+ void testRegisterFactory_wrongTypeStillEmpty() {
+ // given
+ myAdapterManager.registerFactory(new StringToIntFactory());
+
+ // when
+ var result = myAdapterManager.getAdapter("22", Float.class);
+
+ // then
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testUnregisterFactory_providesEmpty() {
+ // given active factory, now gone.
+ StringToIntFactory factory = new StringToIntFactory();
+ myAdapterManager.registerFactory(factory);
+ myAdapterManager.getAdapter("22", Integer.class);
+ myAdapterManager.unregisterFactory(factory);
+
+ // when
+ var result = myAdapterManager.getAdapter("22", Integer.class);
+
+ // then
+ assertThat(result).isEmpty();
+ }
+
+
+ static class StringToIntFactory implements IAdapterFactory {
+ @Override
+ public Optional getAdapter(Object theObject, Class theAdapterType) {
+ if (theObject instanceof String s) {
+ if (theAdapterType.isAssignableFrom(Integer.class)) {
+ @SuppressWarnings("unchecked")
+ T i = (T) Integer.valueOf(s);
+ return Optional.of(i);
+ }
+ }
+ return Optional.empty();
+ }
+
+ public Collection> getAdapters() {
+ return List.of(Integer.class);
+ }
+ }
+}
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java
new file mode 100644
index 00000000000..c7dfef8661b
--- /dev/null
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/adapters/AdapterUtilsTest.java
@@ -0,0 +1,123 @@
+package ca.uhn.fhir.util.adapters;
+
+import jakarta.annotation.Nonnull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class AdapterUtilsTest {
+
+ final private IAdapterFactory myTestFactory = new TestAdaptorFactory();
+
+ @AfterEach
+ void tearDown() {
+ AdapterManager.INSTANCE.unregisterFactory(myTestFactory);
+ }
+
+ @Test
+ void testNullDoesNotAdapt() {
+
+ // when
+ var adapted = AdapterUtils.adapt(null, InterfaceA.class);
+
+ // then
+ assertThat(adapted).isEmpty();
+ }
+
+ @Test
+ void testAdaptObjectImplementingInterface() {
+ // given
+ var object = new ClassB();
+
+ // when
+ var adapted = AdapterUtils.adapt(object, InterfaceA.class);
+
+ // then
+ assertThat(adapted)
+ .isPresent()
+ .get().isInstanceOf(InterfaceA.class);
+ assertThat(adapted.get()).withFailMessage("Use object since it implements interface").isSameAs(object);
+ }
+
+ @Test
+ void testAdaptObjectImplementingAdaptorSupportingInterface() {
+ // given
+ var object = new SelfAdaptableClass();
+
+ // when
+ var adapted = AdapterUtils.adapt(object, InterfaceA.class);
+
+ // then
+ assertThat(adapted)
+ .isPresent()
+ .get().isInstanceOf(InterfaceA.class);
+ }
+
+ @Test
+ void testAdaptObjectViaAdapterManager() {
+ // given
+ var object = new ManagerAdaptableClass();
+ AdapterManager.INSTANCE.registerFactory(myTestFactory);
+
+ // when
+ var adapted = AdapterUtils.adapt(object, InterfaceA.class);
+
+ // then
+ assertThat(adapted)
+ .isPresent()
+ .get().isInstanceOf(InterfaceA.class);
+ }
+
+ interface InterfaceA {
+
+ }
+
+ static class ClassB implements InterfaceA {
+
+ }
+
+ /** class that can adapt itself to IAdaptable */
+ static class SelfAdaptableClass implements IAdaptable {
+
+ @Nonnull
+ @Override
+ public Optional getAdapter(@Nonnull Class theTargetType) {
+ if (theTargetType.isAssignableFrom(InterfaceA.class)) {
+ T value = theTargetType.cast(buildInterfaceAWrapper(this));
+ return Optional.of(value);
+ }
+ return Optional.empty();
+ }
+ }
+
+ private static @Nonnull InterfaceA buildInterfaceAWrapper(Object theObject) {
+ return new InterfaceA() {};
+ }
+
+ /** Class that relies on an external IAdapterFactory */
+ static class ManagerAdaptableClass {
+ }
+
+
+ static class TestAdaptorFactory implements IAdapterFactory {
+
+ @Override
+ public Optional getAdapter(Object theObject, Class theAdapterType) {
+ if (theObject instanceof ManagerAdaptableClass && theAdapterType == InterfaceA.class) {
+ T adapter = theAdapterType.cast(buildInterfaceAWrapper(theObject));
+ return Optional.of(adapter);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Collection> getAdapters() {
+ return Set.of(InterfaceA.class);
+ }
+ }
+}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml
new file mode 100644
index 00000000000..8ef5947d66e
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs-again.yaml
@@ -0,0 +1,4 @@
+---
+type: change
+jira: SMILE-9161
+title: "Contained resources which arrive without assigned IDs are now assigned GUIDs, as opposed to monotonically increasing numeric IDs. This avoids a whole class of issues related to processing order and collisions."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml
new file mode 100644
index 00000000000..1c76ceaa3a8
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6403-json-parser-bugs.yaml
@@ -0,0 +1,6 @@
+---
+type: fix
+jira: SMILE-9161
+title: "Fixed a rare bug in the JSON Parser, wherein client-assigned contained resource IDs could collide with server-assigned contained IDs. For example if a
+resource had a client-assigned contained ID of `#2`, and a contained resource with no ID, then depending on the processing order, the parser could occasionally
+provide duplicate contained resource IDs, leading to non-deterministic behaviour."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml
new file mode 100644
index 00000000000..4d0100526ad
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6419-fix-reindex-optimize-storage-all-versions-posgtres.yaml
@@ -0,0 +1,6 @@
+---
+type: fix
+issue: 6419
+title: "Previously, on Postgres, the `$reindex` operation with `optimizeStorage` set to `ALL_VERSIONS` would process
+only a subset of versions if there were more than 100 versions to be processed for a resource. This has been fixed
+so that all versions of the resource are now processed."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml
new file mode 100644
index 00000000000..ee409dc43ee
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6420-fix-reindex-optimize-storage-all-versions-for-a-single-resource.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 6420
+title: "Previously, when the `$reindex` operation is run for a single FHIR resource with `optimizeStorage` set to
+`ALL_VERSIONS`, none of the versions of the resource were processed in `hfj_res_ver` table. This has been fixed."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml
new file mode 100644
index 00000000000..9b6dc6320ce
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6422-fixes-remote-terminology-issues.yaml
@@ -0,0 +1,7 @@
+---
+type: fix
+issue: 6422
+title: "Previously, since 7.4.4 the validation issue detail codes were not translated correctly for Remote Terminology
+validateCode calls. The detail code used was `invalid-code` for all use-cases which resulted in profile binding strength
+not being applied to the issue severity as expected when validating resources against a profile.
+This has been fixed and issue detail codes are translated correctly."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml
new file mode 100644
index 00000000000..86f16018012
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6440-fix-hooks-not-called-for-precheck-for-cached-search.yaml
@@ -0,0 +1,8 @@
+---
+type: fix
+issue: 6440
+title: "Previously, if an `IInterceptorBroadcaster` was set in a `RequestDetails` object,
+`STORAGE_PRECHECK_FOR_CACHED_SEARCH` hooks that were registered to that `IInterceptorBroadcaster` were not
+called. Also, if an `IInterceptorBroadcaster` was set in the `RequestDetails` object, the boolean return value of the hooks
+registered to that `IInterceptorBroadcaster` were not taken into account. This second issue existed for all pointcuts
+that returned a boolean type, not just for `STORAGE_PRECHECK_FOR_CACHED_SEARCH`. These issues have now been fixed."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml
new file mode 100644
index 00000000000..841287f278d
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6445-repository-api-multimap.yaml
@@ -0,0 +1,4 @@
+---
+type: add
+issue: 6445
+title: "Add Multimap versions of the search() methods to Repository to support queries like `Patient?_tag=a&_tag=b`"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml
new file mode 100644
index 00000000000..ce62822d370
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6451-bulk-import-job-status-not-changing-after-activation.yaml
@@ -0,0 +1,6 @@
+---
+type: fix
+issue: 6451
+jira: SMILE-9089
+title: "Previously, activating `BulkDataImport` job would not change jobs status to `RUNNING`,
+causing it to be processed multiple times instead of single time. This has been fixed."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml
new file mode 100644
index 00000000000..f5c95876cd1
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6467-hibernate-search-lucene-version-incompatibility.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 6467
+title: "Fixed an incompatibility between Hibernate Search and Lucene versions that caused ValueSet expansion to fail
+when Hibernate Search was configured to use Lucene."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml
new file mode 100644
index 00000000000..86a4c3d7a59
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6489-change-index-add-concurrency-default.yaml
@@ -0,0 +1,4 @@
+---
+type: perf
+issue: 6489
+title: "Change the migrator to avoid table locks when adding an index. This allows systems to continue running during upgrade."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml
index c900db52e43..ac97e01fc90 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/changes.yaml
@@ -4,7 +4,7 @@
title: "The version of a few dependencies have been bumped to more recent versions
(dependent HAPI modules listed in brackets):
@@ -33,7 +33,7 @@ Comments: AllergyIntolerance.note[x].text (separated by )
Comments
-
Onset
+
Onset
Onset
diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
index 30791aadc8d..96464a82b4d 100644
--- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
+++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
@@ -220,7 +220,7 @@ public class IpsGeneratorSvcImplTest {
HtmlTable table = (HtmlTable) tables.get(0);
int onsetIndex = 6;
assertEquals("Onset", table.getHeader().getRows().get(0).getCell(onsetIndex).asNormalizedText());
- assertEquals(new DateTimeType("2020-02-03T11:22:33Z").getValue().toString(), table.getBodies().get(0).getRows().get(0).getCell(onsetIndex).asNormalizedText());
+ assertEquals(new DateTimeType("2020-02-03T11:22:33Z").getValueAsString(), table.getBodies().get(0).getRows().get(0).getCell(onsetIndex).asNormalizedText());
assertEquals("Some Onset", table.getBodies().get(0).getRows().get(1).getCell(onsetIndex).asNormalizedText());
assertEquals("", table.getBodies().get(0).getRows().get(2).getCell(onsetIndex).asNormalizedText());
}
From 3b8569127e6ccb2a18a1ec769c6df45a216b8209 Mon Sep 17 00:00:00 2001
From: Michael Buckley
Date: Tue, 26 Nov 2024 13:46:05 -0500
Subject: [PATCH 3/6] Start removing dependency from FhirVersionEnum to
FhirContext (#6512)
Deprecate path from FhirVersionEnum to FhirContext and replace usages.
---
.../src/main/java/ca/uhn/fhir/context/FhirContext.java | 10 +++++++++-
.../main/java/ca/uhn/fhir/context/FhirVersionEnum.java | 6 +++++-
.../narrative2/NarrativeGeneratorTemplateUtils.java | 4 +++-
.../src/main/java/ca/uhn/fhir/cli/BaseCommand.java | 2 +-
.../converters/canonical/VersionCanonicalizer.java | 2 +-
.../fhir/changelog/7_8_0/6512-snip-fhir-context.yaml | 4 ++++
.../ca/uhn/fhir/rest/server/RestfulServerUtils.java | 2 +-
.../src/main/java/ca/uhn/fhir/to/BaseController.java | 2 +-
8 files changed, 25 insertions(+), 7 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6512-snip-fhir-context.yaml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index acc1f898574..796c9f7392e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -1293,7 +1293,15 @@ public class FhirContext {
* @since 5.1.0
*/
public static FhirContext forCached(FhirVersionEnum theFhirVersionEnum) {
- return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, v -> new FhirContext(v));
+ return ourStaticContexts.computeIfAbsent(theFhirVersionEnum, FhirContext::forVersion);
+ }
+
+ /**
+ * An uncached version of forCached()
+ * @return a new FhirContext for theFhirVersionEnum
+ */
+ public static FhirContext forVersion(FhirVersionEnum theFhirVersionEnum) {
+ return new FhirContext(theFhirVersionEnum);
}
private static Collection> toCollection(
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java
index 8666fd77ce0..2c3e0082bcf 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java
@@ -135,15 +135,19 @@ public enum FhirVersionEnum {
/**
* Creates a new FhirContext for this FHIR version
+ * @deprecated since 7.7. Use {@link FhirContext#forVersion(FhirVersionEnum)} instead
*/
+ @Deprecated(forRemoval = true, since = "7.7")
public FhirContext newContext() {
- return new FhirContext(this);
+ return FhirContext.forVersion(this);
}
/**
* Creates a new FhirContext for this FHIR version, or returns a previously created one if one exists. This
* method uses {@link FhirContext#forCached(FhirVersionEnum)} to return a cached instance.
+ * @deprecated since 7.7. Use {@link FhirContext#forCached(FhirVersionEnum)} instead
*/
+ @Deprecated(forRemoval = true, since = "7.7")
public FhirContext newContextCached() {
return FhirContext.forCached(this);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/NarrativeGeneratorTemplateUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/NarrativeGeneratorTemplateUtils.java
index 21cdab2dcde..a6a9406d72b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/NarrativeGeneratorTemplateUtils.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/NarrativeGeneratorTemplateUtils.java
@@ -20,6 +20,7 @@
package ca.uhn.fhir.narrative2;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.BundleUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@@ -42,7 +43,8 @@ public class NarrativeGeneratorTemplateUtils {
* Given a Bundle as input, are any entries present with a given resource type
*/
public boolean bundleHasEntriesWithResourceType(IBaseBundle theBaseBundle, String theResourceType) {
- FhirContext ctx = theBaseBundle.getStructureFhirVersionEnum().newContextCached();
+ FhirVersionEnum fhirVersionEnum = theBaseBundle.getStructureFhirVersionEnum();
+ FhirContext ctx = FhirContext.forCached(fhirVersionEnum);
List> entryResources =
BundleUtil.getBundleEntryUrlsAndResources(ctx, theBaseBundle);
return entryResources.stream()
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java
index be49c375f4e..0077accc776 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseCommand.java
@@ -668,7 +668,7 @@ public abstract class BaseCommand implements Comparable {
protected void parseFhirContext(CommandLine theCommandLine) throws ParseException {
FhirVersionEnum versionEnum = parseFhirVersion(theCommandLine);
- myFhirCtx = versionEnum.newContext();
+ myFhirCtx = FhirContext.forVersion(versionEnum);
}
public abstract void run(CommandLine theCommandLine) throws ParseException, ExecutionException;
diff --git a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java
index 0a01892f056..25c8ab333c1 100644
--- a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java
+++ b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java
@@ -98,7 +98,7 @@ public class VersionCanonicalizer {
private final FhirContext myContext;
public VersionCanonicalizer(FhirVersionEnum theTargetVersion) {
- this(theTargetVersion.newContextCached());
+ this(FhirContext.forCached(theTargetVersion));
}
public VersionCanonicalizer(FhirContext theTargetContext) {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6512-snip-fhir-context.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6512-snip-fhir-context.yaml
new file mode 100644
index 00000000000..71d35b1adf4
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6512-snip-fhir-context.yaml
@@ -0,0 +1,4 @@
+---
+type: remove
+issue: 6512
+title: "The methods on FhirVersionEnum which produces a FhirContext (newContext() ,and newContextCached()) have been deprecated, and will be removed."
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index a88e50327b6..8477293b90b 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -686,7 +686,7 @@ public class RestfulServerUtils {
if (context.getVersion().getVersion() != theForVersion) {
context = myFhirContextMap.get(theForVersion);
if (context == null) {
- context = theForVersion.newContext();
+ context = FhirContext.forVersion(theForVersion);
myFhirContextMap.put(theForVersion, context);
}
}
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
index f51dc5a4500..ba607ace1d9 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
@@ -297,7 +297,7 @@ public class BaseController {
FhirVersionEnum version = theRequest.getFhirVersion(myConfig);
VersionCanonicalizer retVal = myCanonicalizers.get(version);
if (retVal == null) {
- retVal = new VersionCanonicalizer(version.newContext());
+ retVal = new VersionCanonicalizer(FhirContext.forVersion(version));
myCanonicalizers.put(version, retVal);
}
return retVal;
From 061390d76b3e6097dca8decb97a3c8fab81eb637 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Wed, 27 Nov 2024 07:14:48 -0500
Subject: [PATCH 4/6] Add composite interceptor registry (#6511)
* Composite interceptor improvements
* Add composite interceptor registry
* Add changelog
* Composite Interceptor Broadcaster Improvements
* Fix compile error
* Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6511-rework-composite-interceptor-broadcaster.yaml
Co-authored-by: Tadgh
* Address review comments
* Test fixes
* Test fix
* Test fix
---------
Co-authored-by: Tadgh
---
.../ca/uhn/fhir/interceptor/api/Hook.java | 6 +-
.../api/IBaseInterceptorBroadcaster.java | 12 +
.../uhn/fhir/interceptor/api/IPointcut.java | 2 +
.../ca/uhn/fhir/interceptor/api/Pointcut.java | 10 +
.../executor/BaseInterceptorService.java | 283 ++++++++++--------
.../executor/InterceptorService.java | 6 +-
...ork-composite-interceptor-broadcaster.yaml | 8 +
.../export/svc/JpaBulkExportProcessor.java | 4 +-
.../ca/uhn/fhir/jpa/config/SearchConfig.java | 5 +-
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 17 +-
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 33 +-
.../fhir/jpa/dao/FulltextSearchSvcImpl.java | 8 +-
.../dao/expunge/ExpungeEverythingService.java | 15 +-
.../expunge/JpaResourceExpungeService.java | 8 +-
.../jpa/delete/DeleteConflictService.java | 6 +-
.../delete/ThreadSafeResourceDeleterSvc.java | 8 +-
.../search/PersistedJpaBundleProvider.java | 17 +-
.../jpa/search/SearchCoordinatorSvcImpl.java | 20 +-
.../jpa/search/SynchronousSearchSvcImpl.java | 22 +-
.../jpa/search/builder/SearchBuilder.java | 114 +++----
.../StorageInterceptorHooksFacade.java | 19 +-
.../ResourceLinkPredicateBuilder.java | 16 +-
.../predicate/UriPredicateBuilder.java | 21 +-
.../jpa/search/builder/tasks/SearchTask.java | 24 +-
.../svc/JpaBulkExportProcessorTest.java | 19 +-
.../SearchParamExtractorService.java | 21 +-
.../SearchParamExtractorServiceTest.java | 17 +-
.../SubscriptionMatcherInterceptor.java | 13 +-
.../SubscriptionTriggeringSvcImpl.java | 4 +-
.../jpa/provider/SubscriptionsDstu2Test.java | 2 +-
.../ca/uhn/fhir/jpa/search/BaseSearchSvc.java | 2 +-
.../search/SearchCoordinatorSvcImplTest.java | 18 +-
.../search/SynchronousSearchSvcImplTest.java | 6 +-
.../jpa/term/TerminologySvcImplDstu2Test.java | 5 +
.../bulk/BulkDataExportProviderR4Test.java | 13 +-
.../bulk/BulkDataExportProviderR5Test.java | 13 +-
.../jpa/dao/BaseHapiFhirResourceDaoTest.java | 2 +-
.../jpa/dao/r4/BaseComboParamsR4Test.java | 28 +-
.../MdmReadVirtualizationInterceptor.java | 2 +-
.../fhir/mdm/svc/MdmSearchExpansionSvc.java | 24 +-
.../uhn/fhir/mdm/svc/MdmSearchParamSvc.java | 2 +-
.../rest/api/server/SystemRequestDetails.java | 6 +
.../interceptor/ServerInterceptorUtil.java | 23 +-
.../util/CompositeInterceptorBroadcaster.java | 150 +++++-----
.../CompositeInterceptorBroadcasterTest.java | 251 +++++++++++++---
.../jobs/export/BulkDataExportProvider.java | 36 +--
.../DeleteExpungeJobSubmitterImpl.java | 17 +-
.../interceptor/BinaryStorageInterceptor.java | 16 +-
.../binary/svc/BaseBinaryStorageSvcImpl.java | 14 +-
.../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 87 +++---
.../jpa/dao/BaseTransactionProcessor.java | 58 ++--
.../fhir/jpa/dao/MatchResourceUrlService.java | 16 +-
.../fhir/jpa/dao/SearchBuilderFactory.java | 8 +-
.../jpa/dao/tx/HapiTransactionService.java | 28 +-
.../BaseRequestPartitionHelperSvc.java | 100 ++++---
.../dao/tx/HapiTransactionServiceTest.java | 9 +-
.../uhn/fhir/test/utilities/MockInvoker.java | 51 ++++
57 files changed, 1036 insertions(+), 709 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6511-rework-composite-interceptor-broadcaster.yaml
create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/MockInvoker.java
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java
index 009a4a9b134..db3e34b0c5f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Hook.java
@@ -46,7 +46,11 @@ public @interface Hook {
* and allowable values can be positive or negative or 0.
*
* If no order is specified, or the order is set to 0 (the default order),
- * the order specified at the interceptor type level will take precedence.
+ * the order specified at the {@link Interceptor#order() interceptor type level} will be used.
+ *
+ *
+ * Note that if two hook methods have the same order, then the order of execution is undefined. If
+ * order is important, then an order must always be explicitly stated.
*
*/
int order() default Interceptor.DEFAULT_ORDER;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
index ecbedb55b58..8400caa0a4f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
@@ -19,6 +19,7 @@
*/
package ca.uhn.fhir.interceptor.api;
+import java.util.List;
import java.util.function.Supplier;
public interface IBaseInterceptorBroadcaster {
@@ -73,4 +74,15 @@ public interface IBaseInterceptorBroadcaster {
* @since 4.0.0
*/
boolean hasHooks(POINTCUT thePointcut);
+
+ List getInvokersForPointcut(POINTCUT thePointcut);
+
+ interface IInvoker extends Comparable {
+
+ Object invoke(HookParams theParams);
+
+ int getOrder();
+
+ Object getInterceptor();
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IPointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IPointcut.java
index ab5d718091f..382890eb603 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IPointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IPointcut.java
@@ -27,6 +27,8 @@ public interface IPointcut {
@Nonnull
Class> getReturnType();
+ Class> getBooleanReturnTypeForEnum();
+
@Nonnull
List getParameterTypes();
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
index a3baa55eb45..65fe637fe9c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
@@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.validation.ValidationResult;
import jakarta.annotation.Nonnull;
+import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import java.io.Writer;
@@ -3107,6 +3108,10 @@ public enum Pointcut implements IPointcut {
@Nonnull Class> theReturnType,
@Nonnull ExceptionHandlingSpec theExceptionHandlingSpec,
String... theParameterTypes) {
+
+ // This enum uses the lowercase-b boolean type to indicate boolean return pointcuts
+ Validate.isTrue(!theReturnType.equals(Boolean.class), "Return type Boolean not allowed here, must be boolean");
+
myReturnType = theReturnType;
myExceptionHandlingSpec = theExceptionHandlingSpec;
myParameterTypes = Collections.unmodifiableList(Arrays.asList(theParameterTypes));
@@ -3132,6 +3137,11 @@ public enum Pointcut implements IPointcut {
return myReturnType;
}
+ @Override
+ public Class> getBooleanReturnTypeForEnum() {
+ return boolean.class;
+ }
+
@Override
@Nonnull
public List getParameterTypes() {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
index 911164745d4..8698269ec97 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
@@ -20,6 +20,7 @@
package ca.uhn.fhir.interceptor.executor;
import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IBaseInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IBaseInterceptorService;
@@ -57,12 +58,13 @@ import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+
public abstract class BaseInterceptorService & IPointcut>
implements IBaseInterceptorService, IBaseInterceptorBroadcaster {
private static final Logger ourLog = LoggerFactory.getLogger(BaseInterceptorService.class);
@@ -74,12 +76,11 @@ public abstract class BaseInterceptorService & I
AttributeKey.stringKey("hapifhir.interceptor.method_name");
private final List