diff --git a/pom.xml b/pom.xml index d9eff39b565..c0d77bc4b4a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.4.0 + 7.6.0 hapi-fhir-jpaserver-starter @@ -44,15 +44,7 @@ - - - - - - - - - + org.glassfish.jaxb jaxb-runtime 2.3.8 @@ -381,6 +373,14 @@ 1.13.3 + + + io.micrometer + micrometer-registry-prometheus-simpleclient + 1.13.3 + + + com.zaxxer HikariCP diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index 7b4ab085208..8b6569d0f05 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -83,6 +83,7 @@ public class AppProperties { private Subscription subscription = new Subscription(); private Cors cors = null; private Partitioning partitioning = null; + private Boolean validate_resource_status_for_package_upload = true; private Boolean install_transitive_ig_dependencies = true; private Map implementationGuides = null; @@ -106,7 +107,11 @@ public class AppProperties { private final List custom_interceptor_classes = new ArrayList<>(); private final List custom_provider_classes = new ArrayList<>(); + private Boolean upliftedRefchains_enabled = false; + private boolean userRequestRetryVersionConflictsInterceptorEnabled = false; + + private List search_prefetch_thresholds = new ArrayList<>(); public List getCustomInterceptorClasses() { return custom_interceptor_classes; @@ -613,6 +618,14 @@ public void setInstall_transitive_ig_dependencies(boolean install_transitive_ig_ this.install_transitive_ig_dependencies = install_transitive_ig_dependencies; } + public Boolean getValidate_resource_status_for_package_upload() { + return validate_resource_status_for_package_upload; + } + + public void setValidate_resource_status_for_package_upload(Boolean validate_resource_status_for_package_upload) { + this.validate_resource_status_for_package_upload = validate_resource_status_for_package_upload; + } + public Integer getBundle_batch_pool_size() { return this.bundle_batch_pool_size; } @@ -670,6 +683,30 @@ public void setLanguage_search_parameter_enabled(Boolean language_search_paramet this.language_search_parameter_enabled = language_search_parameter_enabled; } + public List getSearch_prefetch_thresholds() { + return this.search_prefetch_thresholds; + } + + public void setSearch_prefetch_thresholds(List thePrefetchThresholds) { + this.search_prefetch_thresholds = thePrefetchThresholds; + } + + public boolean getUpliftedRefchains_enabled() { + return upliftedRefchains_enabled; + } + + public void setUpliftedRefchains_enabled(boolean upliftedRefchains_enabled) { + this.upliftedRefchains_enabled = upliftedRefchains_enabled; + } + + public Boolean getUserRequestRetryVersionConflictsInterceptorEnabled() { + return userRequestRetryVersionConflictsInterceptorEnabled; + } + + public void setUserRequestRetryVersionConflictsInterceptorEnabled(Boolean userRequestRetryVersionConflictsInterceptorEnabled) { + this.userRequestRetryVersionConflictsInterceptorEnabled = userRequestRetryVersionConflictsInterceptorEnabled; + } + public static class Cors { private Boolean allow_Credentials = true; private List allowed_origin = List.of("*"); @@ -689,8 +726,6 @@ public Boolean getAllow_Credentials() { public void setAllow_Credentials(Boolean allow_Credentials) { this.allow_Credentials = allow_Credentials; } - - } public static class Oauth { @@ -951,6 +986,7 @@ public static class Partitioning { private Boolean partitioning_include_in_search_hashes = false; private Boolean allow_references_across_partitions = false; + private Boolean conditional_create_duplicate_identifiers_enabled = false; public Boolean getPartitioning_include_in_search_hashes() { return partitioning_include_in_search_hashes; @@ -966,6 +1002,14 @@ public Boolean getAllow_references_across_partitions() { public void setAllow_references_across_partitions(Boolean allow_references_across_partitions) { this.allow_references_across_partitions = allow_references_across_partitions; } + + public Boolean getConditional_create_duplicate_identifiers_enabled() { + return conditional_create_duplicate_identifiers_enabled; + } + + public void setConditional_create_duplicate_identifiers_enabled(Boolean conditional_create_duplicate_identifiers_enabled) { + this.conditional_create_duplicate_identifiers_enabled = conditional_create_duplicate_identifiers_enabled; + } } public static class Subscription { diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java index b6b8cf754bf..1dc2fdbf10d 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigCommon.java @@ -151,7 +151,12 @@ public JpaStorageSettings jpaStorageSettings(AppProperties appProperties) { jpaStorageSettings.setDeleteExpungeEnabled(appProperties.getDelete_expunge_enabled()); jpaStorageSettings.setExpungeEnabled(appProperties.getExpunge_enabled()); jpaStorageSettings.setLanguageSearchParameterEnabled(appProperties.getLanguage_search_parameter_enabled()); + jpaStorageSettings.setValidateResourceStatusForPackageUpload(appProperties.getValidate_resource_status_for_package_upload()); + jpaStorageSettings.setIndexOnUpliftedRefchains(appProperties.getUpliftedRefchains_enabled()); + if (!appProperties.getSearch_prefetch_thresholds().isEmpty()) { + jpaStorageSettings.setSearchPreFetchThresholds(appProperties.getSearch_prefetch_thresholds()); + } Integer maxFetchSize = appProperties.getMax_page_size(); jpaStorageSettings.setFetchSizeDefaultMaximum(maxFetchSize); @@ -244,6 +249,8 @@ public PartitionSettings partitionSettings(AppProperties appProperties) { } else { retVal.setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode.NOT_ALLOWED); } + retVal.setConditionalCreateDuplicateIdentifiersEnabled( + appProperties.getPartitioning().getConditional_create_duplicate_identifiers_enabled()); } return retVal; diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java index 2ef61d33c18..d94d16659bd 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.starter.common; +import ca.uhn.fhir.batch2.config.Batch2JobRegisterer; import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry; import ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider; import ca.uhn.fhir.batch2.jobs.imprt.BulkDataImportProvider; @@ -28,6 +29,7 @@ import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.interceptor.UserRequestRetryVersionConflictsInterceptor; import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; import ca.uhn.fhir.jpa.model.config.SubscriptionSettings; @@ -50,7 +52,6 @@ import ca.uhn.fhir.jpa.starter.interceptor.SmartWellKnownInterceptor; import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider; import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper; -import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider; import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; @@ -71,6 +72,8 @@ import com.google.common.base.Strings; import jakarta.persistence.EntityManagerFactory; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -94,7 +97,7 @@ @Import(ThreadPoolFactoryConfig.class) public class StarterJpaConfig { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StarterJpaConfig.class); + private static final Logger ourLog = LoggerFactory.getLogger(StarterJpaConfig.class); @Bean public IFulltextSearchSvc fullTextSearchSvc() { @@ -196,11 +199,11 @@ public LoggingInterceptor loggingInterceptor(AppProperties appProperties) { @Primary @Conditional(OnImplementationGuidesPresent.class) public IPackageInstallerSvc packageInstaller( - AppProperties appProperties, - JobDefinition reindexJobParametersJobDefinition, - JobDefinitionRegistry jobDefinitionRegistry, - IPackageInstallerSvc packageInstallerSvc) { - jobDefinitionRegistry.addJobDefinitionIfNotRegistered(reindexJobParametersJobDefinition); + AppProperties appProperties, + IPackageInstallerSvc packageInstallerSvc, + Batch2JobRegisterer batch2JobRegisterer) { + + batch2JobRegisterer.start(); if (appProperties.getImplementationGuides() != null) { Map guides = appProperties.getImplementationGuides(); @@ -481,6 +484,10 @@ public RestfulServer restfulServer( fhirServer.registerProvider(theIpsOperationProvider.get()); } + if (appProperties.getUserRequestRetryVersionConflictsInterceptorEnabled() ) { + fhirServer.registerInterceptor(new UserRequestRetryVersionConflictsInterceptor()); + } + // register custom providers registerCustomProviders(fhirServer, appContext, appProperties.getCustomProviderClasses()); diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CareGapsProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CareGapsProperties.java index fe4565b08fe..f25d7b2b73f 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CareGapsProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CareGapsProperties.java @@ -1,22 +1,22 @@ package ca.uhn.fhir.jpa.starter.cr; public class CareGapsProperties { - private String reporter = "default"; + private String reporter = "default"; private String section_author = "default"; - public String getReporter() { - return reporter; - } + public String getReporter() { + return reporter; + } - public void setReporter(String reporter) { - this.reporter = reporter; - } + public void setReporter(String reporter) { + this.reporter = reporter; + } - public String getSection_author() { - return section_author; - } + public String getSection_author() { + return section_author; + } - public void setSection_author(String section_author) { - this.section_author = section_author; - } + public void setSection_author(String section_author) { + this.section_author = section_author; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlCompilerProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlCompilerProperties.java index ce44ca30cd8..f392b04978a 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlCompilerProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlCompilerProperties.java @@ -5,7 +5,7 @@ import org.cqframework.cql.cql2elm.LibraryBuilder; public class CqlCompilerProperties { - private Boolean validate_units = true; + private Boolean validate_units = true; private Boolean verify_only = false; private String compatibility_level = "1.5"; private CqlCompilerException.ErrorSeverity error_level = CqlCompilerException.ErrorSeverity.Info; @@ -27,7 +27,6 @@ public class CqlCompilerProperties { private Boolean require_from_keyword = false; private Boolean disable_default_model_info_load = false; - public boolean isValidateUnits() { return validate_units; } @@ -194,5 +193,5 @@ public CqlTranslator.Format getTranslatorFormat() { public void setTranslatorFormat(CqlTranslator.Format translatorFormat) { this.translator_format = translatorFormat; - } + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlProperties.java index 53f297fd871..845e0c37a9c 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlProperties.java @@ -5,49 +5,49 @@ public class CqlProperties { - private Boolean use_embedded_libraries = true; - private CqlCompilerProperties compiler = new CqlCompilerProperties(); - private CqlRuntimeProperties runtime = new CqlRuntimeProperties(); - private TerminologySettings terminology = new TerminologySettings(); - private RetrieveSettings data = new RetrieveSettings(); - - public Boolean getUse_embedded_libraries() { - return use_embedded_libraries; - } - - public void setUse_embedded_libraries(Boolean use_embedded_libraries) { - this.use_embedded_libraries = use_embedded_libraries; - } - - public CqlCompilerProperties getCompiler() { - return compiler; - } - - public void setCompiler(CqlCompilerProperties compiler) { - this.compiler = compiler; - } - - public CqlRuntimeProperties getRuntime() { - return runtime; - } - - public void setRuntime(CqlRuntimeProperties runtime) { - this.runtime = runtime; - } - - public TerminologySettings getTerminology() { - return terminology; - } - - public void setTerminology(TerminologySettings terminology) { - this.terminology = terminology; - } - - public RetrieveSettings getData() { - return data; - } - - public void setData(RetrieveSettings data) { - this.data = data; - } + private Boolean use_embedded_libraries = true; + private CqlCompilerProperties compiler = new CqlCompilerProperties(); + private CqlRuntimeProperties runtime = new CqlRuntimeProperties(); + private TerminologySettings terminology = new TerminologySettings(); + private RetrieveSettings data = new RetrieveSettings(); + + public Boolean getUse_embedded_libraries() { + return use_embedded_libraries; + } + + public void setUse_embedded_libraries(Boolean use_embedded_libraries) { + this.use_embedded_libraries = use_embedded_libraries; + } + + public CqlCompilerProperties getCompiler() { + return compiler; + } + + public void setCompiler(CqlCompilerProperties compiler) { + this.compiler = compiler; + } + + public CqlRuntimeProperties getRuntime() { + return runtime; + } + + public void setRuntime(CqlRuntimeProperties runtime) { + this.runtime = runtime; + } + + public TerminologySettings getTerminology() { + return terminology; + } + + public void setTerminology(TerminologySettings terminology) { + this.terminology = terminology; + } + + public RetrieveSettings getData() { + return data; + } + + public void setData(RetrieveSettings data) { + this.data = data; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlRuntimeProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlRuntimeProperties.java index f0dbfdb9af4..0677d05e9d6 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlRuntimeProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlRuntimeProperties.java @@ -2,7 +2,7 @@ public class CqlRuntimeProperties { - private Boolean debug_logging_enabled = false; + private Boolean debug_logging_enabled = false; private Boolean enable_validation = false; private Boolean enable_expression_caching = true; @@ -14,7 +14,6 @@ public void setDebugLoggingEnabled(boolean debug_logging_enabled) { this.debug_logging_enabled = debug_logging_enabled; } - public boolean isEnableExpressionCaching() { return enable_expression_caching; } @@ -30,6 +29,4 @@ public boolean isEnableValidation() { public void EnableValidation(boolean enable_validation) { this.enable_validation = enable_validation; } - - } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrCommonConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrCommonConfig.java index 59274ed8670..26d7f5f9fa6 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrCommonConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrCommonConfig.java @@ -1,13 +1,14 @@ package ca.uhn.fhir.jpa.starter.cr; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - +import ca.uhn.fhir.cr.common.CodeCacheResourceChangeListener; +import ca.uhn.fhir.cr.common.CqlThreadFactory; +import ca.uhn.fhir.cr.common.ElmCacheResourceChangeListener; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry; +import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryInterceptor; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import org.cqframework.cql.cql2elm.CqlCompilerOptions; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.cqframework.cql.cql2elm.model.Model; @@ -28,15 +29,13 @@ import org.springframework.context.annotation.Primary; import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; -import ca.uhn.fhir.cr.common.CodeCacheResourceChangeListener; -import ca.uhn.fhir.cr.common.CqlThreadFactory; -import ca.uhn.fhir.cr.common.ElmCacheResourceChangeListener; -import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry; -import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryInterceptor; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Configuration @Conditional({CrConfigCondition.class}) @@ -157,8 +156,12 @@ public ExecutorService cqlExecutor() { @Bean CareGapsProperties careGapsProperties(CrProperties theCrProperties) { var careGapsProperties = new CareGapsProperties(); - careGapsProperties.setCareGapsReporter(theCrProperties.getCareGaps().getReporter()); - careGapsProperties.setCareGapsCompositionSectionAuthor(theCrProperties.getCareGaps().getSection_author()); + // This check for the resource type really should be happening down in CR where the setting is actually used but + // that will have to wait for a future CR release + careGapsProperties.setCareGapsReporter( + theCrProperties.getCareGaps().getReporter().replace("Organization/", "")); + careGapsProperties.setCareGapsCompositionSectionAuthor( + theCrProperties.getCareGaps().getSection_author().replace("Organization/", "")); return careGapsProperties; } @@ -224,5 +227,5 @@ public CodeCacheResourceChangeListener codeCacheResourceChangeListener( @Bean public ResourceChangeListenerRegistryInterceptor resourceChangeListenerRegistryInterceptor() { return new ResourceChangeListenerRegistryInterceptor(); - } + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrProperties.java index 58ca3c2e82e..8ba5ccf0e79 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/CrProperties.java @@ -1,9 +1,5 @@ package ca.uhn.fhir.jpa.starter.cr; -import org.cqframework.cql.cql2elm.CqlCompilerException; -import org.cqframework.cql.cql2elm.CqlTranslator; -import org.cqframework.cql.cql2elm.LibraryBuilder; - public class CrProperties { private Boolean enabled; diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrDstu3Config.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrDstu3Config.java index 77e91bbfcfe..44f7d42a01c 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrDstu3Config.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrDstu3Config.java @@ -2,10 +2,9 @@ import ca.uhn.fhir.cr.config.dstu3.ApplyOperationConfig; import ca.uhn.fhir.cr.config.dstu3.CrDstu3Config; -import ca.uhn.fhir.cr.config.dstu3.ExtractOperationConfig; +import ca.uhn.fhir.cr.config.dstu3.DataRequirementsOperationConfig; +import ca.uhn.fhir.cr.config.dstu3.EvaluateOperationConfig; import ca.uhn.fhir.cr.config.dstu3.PackageOperationConfig; -import ca.uhn.fhir.cr.config.dstu3.PopulateOperationConfig; -import ca.uhn.fhir.cr.config.dstu3.QuestionnaireOperationConfig; import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; import org.springframework.context.annotation.*; @@ -15,11 +14,8 @@ CrCommonConfig.class, CrDstu3Config.class, ApplyOperationConfig.class, - ExtractOperationConfig.class, - PackageOperationConfig.class, - PopulateOperationConfig.class, - QuestionnaireOperationConfig.class + DataRequirementsOperationConfig.class, + EvaluateOperationConfig.class, + PackageOperationConfig.class }) -public class StarterCrDstu3Config { - -} +public class StarterCrDstu3Config {} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrR4Config.java b/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrR4Config.java index ee53bf2e872..28c10cf9b82 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrR4Config.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrR4Config.java @@ -2,6 +2,8 @@ import ca.uhn.fhir.cr.config.r4.ApplyOperationConfig; import ca.uhn.fhir.cr.config.r4.CrR4Config; +import ca.uhn.fhir.cr.config.r4.DataRequirementsOperationConfig; +import ca.uhn.fhir.cr.config.r4.EvaluateOperationConfig; import ca.uhn.fhir.cr.config.r4.ExtractOperationConfig; import ca.uhn.fhir.cr.config.r4.PackageOperationConfig; import ca.uhn.fhir.cr.config.r4.PopulateOperationConfig; @@ -17,11 +19,11 @@ CrCommonConfig.class, CrR4Config.class, ApplyOperationConfig.class, + DataRequirementsOperationConfig.class, + EvaluateOperationConfig.class, ExtractOperationConfig.class, PackageOperationConfig.class, PopulateOperationConfig.class, QuestionnaireOperationConfig.class }) -public class StarterCrR4Config { - -} +public class StarterCrR4Config {} diff --git a/src/main/resources/application-custom.yaml b/src/main/resources/application-custom.yaml index a294fd2ab45..3c9fb91af1d 100644 --- a/src/main/resources/application-custom.yaml +++ b/src/main/resources/application-custom.yaml @@ -1,31 +1,37 @@ #Uncomment the "servlet" and "context-path" lines below to make the fhir endpoint available at /example/path/fhir instead of the default value of /fhir server: -# servlet: -# context-path: /example/path + # servlet: + # context-path: /example/path port: 8080 #Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints management: + #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. + endpoints: + enabled-by-default: false + web: + exposure: + include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' endpoint: - endpoints: - enabled-by-default: false - web: - exposure: - include: health,prometheus + info: + enabled: true + metrics: + enabled: true health: enabled: true probes: enabled: true - livenessState: - enabled: true - readinessState: - enabled: true + group: + liveness: + include: + - livenessState + - readinessState prometheus: enabled: true - metrics: - export: - enabled: true - + prometheus: + metrics: + export: + enabled: true spring: main: allow-circular-references: true @@ -123,7 +129,7 @@ hapi: valueset_membership_mode: USE_EXPANSION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_EXPANSION code_lookup_mode: USE_VALIDATE_CODE_OPERATION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_CODESYSTEM_URL data: - search_parameter_mode: FILTER_IN_MEMORY # AUTO, USE_SEARCH_PARAMETERS, FILTER_IN_MEMORY + search_parameter_mode: USE_SEARCH_PARAMETERS # AUTO, USE_SEARCH_PARAMETERS, FILTER_IN_MEMORY terminology_parameter_mode: FILTER_IN_MEMORY # AUTO, USE_VALUE_SET_URL, USE_INLINE_CODES, FILTER_IN_MEMORY profile_mode: DECLARED # ENFORCED, DECLARED, OPTIONAL, TRUST, OFF @@ -155,6 +161,8 @@ hapi: # server_path: /baseR4 # server_address: http://hapi.fhir.org/baseR4 # defer_indexing_for_codesystems_of_size: 101 + ### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed. + # validate_resource_status_for_package_upload: false # install_transitive_ig_dependencies: true #implementationguides: ### example from registry (packages.fhir.org) @@ -193,6 +201,7 @@ hapi: # enable_index_missing_fields: false # enable_index_of_type: true # enable_index_contained_resource: false + # upliftedRefchains_enabled: true # resource_dbhistory_enabled: false ### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html @@ -214,6 +223,8 @@ hapi: narrative_enabled: false mdm_enabled: false mdm_rules_json_location: "mdm-rules.json" + ## see: https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#jpa-server-retry-on-version-conflicts + # userRequestRetryVersionConflictsInterceptorEnabled : false # local_base_urls: # - https://hapi.fhir.org/baseR4 logical_urls: @@ -228,6 +239,7 @@ hapi: # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false + # conditional_create_duplicate_identifiers_enabled: false cors: allow_Credentials: true # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- @@ -239,6 +251,17 @@ hapi: search-coord-max-pool-size: 100 search-coord-queue-capacity: 200 + # Search Prefetch Thresholds. + + # This setting sets the number of search results to prefetch. For example, if this list + # is set to [100, 1000, -1] then the server will initially load 100 results and not + # attempt to load more. If the user requests subsequent page(s) of results and goes + # past 100 results, the system will load the next 900 (up to the following threshold of 1000). + # The system will progressively work through these thresholds. + # A threshold of -1 means to load all results. Note that if the final threshold is a + # number other than -1, the system will never prefetch more than the given number. + search_prefetch_thresholds: 13,503,2003,-1 + # comma-separated package names, will be @ComponentScan'ed by Spring to allow for creating custom Spring beans #custom-bean-packages: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f395ea3d278..80bc2d7627a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,31 +1,37 @@ #Uncomment the "servlet" and "context-path" lines below to make the fhir endpoint available at /example/path/fhir instead of the default value of /fhir server: -# servlet: -# context-path: /example/path + # servlet: + # context-path: /example/path port: 8080 #Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints management: + #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. + endpoints: + enabled-by-default: false + web: + exposure: + include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' endpoint: - endpoints: - enabled-by-default: false - web: - exposure: - include: health,prometheus + info: + enabled: true + metrics: + enabled: true health: enabled: true probes: enabled: true - livenessState: - enabled: true - readinessState: - enabled: true + group: + liveness: + include: + - livenessState + - readinessState prometheus: enabled: true - metrics: - export: - enabled: true - + prometheus: + metrics: + export: + enabled: true spring: main: allow-circular-references: true @@ -115,7 +121,7 @@ hapi: valueset_membership_mode: USE_EXPANSION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_EXPANSION code_lookup_mode: USE_VALIDATE_CODE_OPERATION # AUTO, USE_VALIDATE_CODE_OPERATION, USE_CODESYSTEM_URL data: - search_parameter_mode: FILTER_IN_MEMORY # AUTO, USE_SEARCH_PARAMETERS, FILTER_IN_MEMORY + search_parameter_mode: USE_SEARCH_PARAMETERS # AUTO, USE_SEARCH_PARAMETERS, FILTER_IN_MEMORY terminology_parameter_mode: FILTER_IN_MEMORY # AUTO, USE_VALUE_SET_URL, USE_INLINE_CODES, FILTER_IN_MEMORY profile_mode: DECLARED # ENFORCED, DECLARED, OPTIONAL, TRUST, OFF @@ -147,6 +153,8 @@ hapi: # server_path: /baseR4 # server_address: http://hapi.fhir.org/baseR4 # defer_indexing_for_codesystems_of_size: 101 + ### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed. + # validate_resource_status_for_package_upload: false # install_transitive_ig_dependencies: true #implementationguides: ### example from registry (packages.fhir.org) @@ -185,6 +193,7 @@ hapi: # enable_index_missing_fields: false # enable_index_of_type: true # enable_index_contained_resource: false + # upliftedRefchains_enabled: true # resource_dbhistory_enabled: false ### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html @@ -206,6 +215,8 @@ hapi: narrative_enabled: false mdm_enabled: false mdm_rules_json_location: "mdm-rules.json" + ## see: https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#jpa-server-retry-on-version-conflicts + # userRequestRetryVersionConflictsInterceptorEnabled : false # local_base_urls: # - https://hapi.fhir.org/baseR4 logical_urls: @@ -220,6 +231,7 @@ hapi: # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false + # conditional_create_duplicate_identifiers_enabled: false cors: allow_Credentials: true # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- @@ -231,6 +243,17 @@ hapi: search-coord-max-pool-size: 100 search-coord-queue-capacity: 200 + # Search Prefetch Thresholds. + + # This setting sets the number of search results to prefetch. For example, if this list + # is set to [100, 1000, -1] then the server will initially load 100 results and not + # attempt to load more. If the user requests subsequent page(s) of results and goes + # past 100 results, the system will load the next 900 (up to the following threshold of 1000). + # The system will progressively work through these thresholds. + # A threshold of -1 means to load all results. Note that if the final threshold is a + # number other than -1, the system will never prefetch more than the given number. + search_prefetch_thresholds: 13,503,2003,-1 + # comma-separated package names, will be @ComponentScan'ed by Spring to allow for creating custom Spring beans #custom-bean-packages: diff --git a/src/main/resources/cds.application.yaml b/src/main/resources/cds.application.yaml index b1703d86c4c..e94030c4478 100644 --- a/src/main/resources/cds.application.yaml +++ b/src/main/resources/cds.application.yaml @@ -6,10 +6,32 @@ server: #Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints management: + #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. endpoints: + enabled-by-default: false web: exposure: - include: "health,prometheus" + include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' + endpoint: + info: + enabled: true + metrics: + enabled: true + health: + enabled: true + probes: + enabled: true + group: + liveness: + include: + - livenessState + - readinessState + prometheus: + enabled: true + prometheus: + metrics: + export: + enabled: true spring: main: allow-circular-references: true @@ -131,6 +153,8 @@ hapi: ### enable to set the Server URL # server_address: http://hapi.fhir.org/baseR4 # defer_indexing_for_codesystems_of_size: 101 + ### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed. + # validate_resource_status_for_package_upload: false # install_transitive_ig_dependencies: true #implementationguides: ### example from registry (packages.fhir.org) @@ -153,7 +177,7 @@ hapi: # allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET # allow_cascading_deletes: true # allow_contains_searches: true - # allow_external_references: true + allow_external_references: true # allow_multiple_delete: true # allow_override_default_search_params: true # auto_create_placeholder_reference_targets: false @@ -203,6 +227,7 @@ hapi: # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false + # conditional_create_duplicate_identifiers_enabled: false cors: allow_Credentials: true # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List- diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/CdsHooksServletIT.java b/src/test/java/ca/uhn/fhir/jpa/starter/CdsHooksServletIT.java index 129a63258c7..65714878974 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/CdsHooksServletIT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/CdsHooksServletIT.java @@ -88,7 +88,14 @@ void beforeEach() { private Boolean hasCdsServices() throws IOException { var response = callCdsServicesDiscovery(); - return response.getEntity().getContentLength() > 21 || response.getEntity().isChunked(); + + // NOTE: this is looking for a repsonse that indicates there are CDS services availalble. + // And empty response looks like: {"services": []} + // Looking at the actual response string consumes the InputStream which has side-effects, making it tricky to compare the actual contents. + // Hence the test just looks at the length to make this determination. + // The actual response has newlines in it which vary in size on some systems, but a value of 25 seems to work across linux/mac/windows + // to ensure the repsonse actually contains CDS services in it + return response.getEntity().getContentLength() > 25 || response.getEntity().isChunked(); } private CloseableHttpResponse callCdsServicesDiscovery() { diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java index 9d4c033c436..b8279468869 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java @@ -16,6 +16,7 @@ import java.util.List; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch.indices.IndexSettings; import co.elastic.clients.json.JsonData; import jakarta.annotation.PreDestroy; import org.elasticsearch.client.RequestOptions; @@ -91,7 +92,7 @@ public static void beforeClass() throws IOException { elasticsearchHighLevelRestClient.indices().putTemplate(t->{ t.name("hapi_fhir_template"); t.indexPatterns("*"); - t.settings("index.max_result_window", JsonData.of(50000)); + t.settings(new IndexSettings.Builder().maxResultWindow(50000).build()); return t; }); diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java index d7b46315dbd..041a4fce646 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -13,6 +13,10 @@ import jakarta.websocket.ContainerProvider; import jakarta.websocket.Session; import jakarta.websocket.WebSocketContainer; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; @@ -29,6 +33,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; @@ -63,6 +69,7 @@ "hapi.fhir.cr.enabled=true", "hapi.fhir.cr.caregaps.section_author=Organization/alphora-author", "hapi.fhir.cr.caregaps.reporter=Organization/alphora", + "hapi.fhir.cr.cql.data.search_parameter_mode=USE_SEARCH_PARAMETERS", "hapi.fhir.implementationguides.dk-core.name=hl7.fhir.dk.core", "hapi.fhir.implementationguides.dk-core.version=1.1.0", "hapi.fhir.auto_create_placeholder_reference_targets=true", @@ -306,6 +313,17 @@ private int activeSubscriptionCount() { .size(); } + @ParameterizedTest + @ValueSource(strings = {"prometheus", "health", "metrics", "info"}) + void testActuatorEndpointExists(String endpoint) throws IOException { + + CloseableHttpClient httpclient = HttpClients.createDefault(); + CloseableHttpResponse response = httpclient.execute(new HttpGet("http://localhost:" + port + "/actuator/" + endpoint)); + int statusCode = response.getStatusLine().getStatusCode(); + assertEquals(200, statusCode); + + } + @BeforeEach void beforeEach() { diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ParallelUpdatesVersionConflictTest.java b/src/test/java/ca/uhn/fhir/jpa/starter/ParallelUpdatesVersionConflictTest.java new file mode 100644 index 00000000000..29dd059bff0 --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ParallelUpdatesVersionConflictTest.java @@ -0,0 +1,164 @@ +package ca.uhn.fhir.jpa.starter; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.Bundle.HTTPVerb; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { + "spring.datasource.url=jdbc:h2:mem:dbr4", + "hapi.fhir.fhir_version=r4", + "hapi.fhir.userRequestRetryVersionConflictsInterceptorEnabled=true" +}) + +/** + * This class tests running parallel updates to a single resource with and without setting the 'X-Retry-On-Version-Conflict' header + * to ensure we get the expected behavior of detecting conflicts + */ +public class ParallelUpdatesVersionConflictTest { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParallelUpdatesVersionConflictTest.class); + + @LocalServerPort + private int port; + + private IGenericClient client; + private FhirContext ctx; + + @BeforeEach + void setUp() { + ctx = FhirContext.forR4(); + ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ctx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/fhir/"; + client = ctx.newRestfulGenericClient(ourServerBase); + } + + @Test + void testParallelResourceUpdateBundle() throws Throwable { + //send 10 bundles with updates to the patient in parallel, except the header to deconflict them + Patient pat = new Patient(); + String patId = client.create().resource(pat).execute().getId().getIdPart(); + launchThreads(patId, true, "X-Retry-On-Version-Conflict"); + } + + @Test + void testParallelResourceUpdateNoBundle() throws Throwable { + //send 10 resource puts to the patient in parallel, except the header to deconflict them + Patient pat = new Patient(); + String patId = client.create().resource(pat).execute().getId().getIdPart(); + launchThreads(patId, false, "X-Retry-On-Version-Conflict"); + } + + @Test + void testParallelResourceUpdateBundleExpectConflict() { + //send 10 bundles with updates to the patient in parallel, expect a ResourceVersionConflictException since we are not setting the retry header + Patient pat = new Patient(); + String patId = client.create().resource(pat).execute().getId().getIdPart(); + ResourceVersionConflictException exception = assertThrows(ResourceVersionConflictException.class, () -> + launchThreads(patId, true, "someotherheader")); + } + + @Test + void testParallelResourceUpdateNoBundleExpectConflict() { + //send 10 resource puts to the patient in parallel, expect a ResourceVersionConflictException since we are not setting the retry header + Patient pat = new Patient(); + String patId = client.create().resource(pat).execute().getId().getIdPart(); + ResourceVersionConflictException exception = assertThrows(ResourceVersionConflictException.class, () -> + launchThreads(patId, false, "someotherheader")); + } + + private void launchThreads(String patientId, boolean useBundles, String headerName) throws Throwable { + int threadCnt = 10; + ExecutorService execSvc = Executors.newFixedThreadPool(threadCnt); + + //launch a bunch of threads at the same time that update the same patient + List> callables = new ArrayList<>(); + for (int i = 0; i < threadCnt; i++) { + final int cnt = i; + Callable callable = new Callable<>() { + @Override + public Integer call() throws Exception { + Patient pat = new Patient(); + //make sure to change something so the server doesnt short circuit on a no-op + pat.addName().setFamily("fam-" + cnt); + pat.setId(patientId); + + if( useBundles) { + Bundle b = new Bundle(); + b.setType(BundleType.TRANSACTION); + BundleEntryComponent bec = b.addEntry(); + bec.setResource(pat); + //bec.setFullUrl("Patient/" + patId); + Bundle.BundleEntryRequestComponent req = bec.getRequest(); + req.setUrl("Patient/" + patientId); + req.setMethod(HTTPVerb.PUT); + bec.setRequest(req); + + Bundle returnBundle = client.transaction().withBundle(b) + .withAdditionalHeader(headerName, "retry; max-retries=10") + .execute(); + + String statusString = returnBundle.getEntryFirstRep().getResponse().getStatus(); + ourLog.trace("statusString->{}", statusString); + try { + return Integer.parseInt(statusString.substring(0,3)); + }catch(NumberFormatException nfe) { + return 500; + } + } + else { + MethodOutcome outcome = client.update().resource(pat).withId(patientId) + .withAdditionalHeader(headerName, "retry; max-retries=10") + .execute(); + ourLog.trace("updated patient: " + outcome.getResponseStatusCode()); + return outcome.getResponseStatusCode(); + } + } + }; + callables.add(callable); + } + + List> futures = new ArrayList<>(); + + //launch them all at once + for (Callable callable : callables) { + futures.add(execSvc.submit(callable)); + } + + //wait for calls to complete + for (Future future : futures) { + try { + Integer httpResponseCode = future.get(); + Assertions.assertEquals(200, httpResponseCode); + } catch (InterruptedException | ExecutionException e) { + //throw the ResourceVersionConflictException back up so we can test it + throw e.getCause(); + } + } + } +} diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 911d9709034..a4943599298 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -1,3 +1,30 @@ +management: + #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus + endpoints: + enabled-by-default: false + web: + exposure: + include: 'info,health,prometheus,metrics' # or '*' for all' + endpoint: + info: + enabled: true + metrics: + enabled: true + health: + enabled: true + probes: + enabled: true + group: + liveness: + include: + - livenessState + - readinessState + prometheus: + enabled: true + prometheus: + metrics: + export: + enabled: true spring: main: allow-circular-references: true @@ -67,6 +94,8 @@ hapi: ### enable to set the Server URL # server_address: http://hapi.fhir.org/baseR4 # defer_indexing_for_codesystems_of_size: 101 + ### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed. + # validate_resource_status_for_package_upload: false # install_transitive_ig_dependencies: true # implementationguides: ### example from registry (packages.fhir.org) @@ -119,6 +148,7 @@ hapi: # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false + # partitioning_include_in_search_hashes #cors: # allow_Credentials: true # These are allowed_origin patterns, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/cors/CorsConfiguration.html#setAllowedOriginPatterns-java.util.List-