diff --git a/pom.xml b/pom.xml index d52cc93..771de2a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.6.0 + 8.0.0 hapi-fhir-jpaserver-starter 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 f4aa66d..58c5bac 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -23,6 +23,11 @@ import java.util.Set; @EnableConfigurationProperties public class AppProperties { + private final Set auto_version_reference_at_paths = new HashSet<>(); + private final Set local_base_urls = new HashSet<>(); + private final Set logical_urls = new HashSet<>(); + private final List custom_interceptor_classes = new ArrayList<>(); + private final List custom_provider_classes = new ArrayList<>(); private Boolean cr_enabled = false; private Boolean ips_enabled = false; private Boolean openapi_enabled = false; @@ -37,7 +42,6 @@ public class AppProperties { private Boolean allow_override_default_search_params = true; private Boolean auto_create_placeholder_reference_targets = false; private Boolean mass_ingestion_mode_enabled = false; - private final Set auto_version_reference_at_paths = new HashSet<>(); private Boolean language_search_parameter_enabled = false; private Boolean dao_scheduling_enabled = true; private Boolean delete_expunge_enabled = false; @@ -70,9 +74,7 @@ public class AppProperties { private List supported_resource_types = new ArrayList<>(); private List allowed_bundle_types = null; private Boolean narrative_enabled = true; - private Boolean ig_runtime_upload_enabled = false; - private Validation validation = new Validation(); private Map tester = null; private Logger logger = new Logger(); @@ -82,28 +84,17 @@ public class AppProperties { private Boolean validate_resource_status_for_package_upload = true; private Boolean install_transitive_ig_dependencies = true; private Map implementationGuides = null; - private String custom_content_path = null; private String app_content_path = null; - private Boolean lastn_enabled = false; private boolean store_resource_in_lucene_index_enabled = false; private NormalizedQuantitySearchLevel normalized_quantity_search_level = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED; - private Boolean use_apache_address_strategy = false; private Boolean use_apache_address_strategy_https = false; - private Integer bundle_batch_pool_size = 20; private Integer bundle_batch_pool_max_size = 100; - private final Set local_base_urls = new HashSet<>(); - private final Set logical_urls = new HashSet<>(); - private Boolean resource_dbhistory_enabled = true; - - 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; @@ -226,6 +217,10 @@ public class AppProperties { return subscription; } + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + public Boolean getDefault_pretty_print() { return default_pretty_print; } @@ -234,10 +229,6 @@ public class AppProperties { this.default_pretty_print = default_pretty_print; } - public void setSubscription(Subscription subscription) { - this.subscription = subscription; - } - public Validation getValidation() { return validation; } @@ -671,6 +662,22 @@ public class AppProperties { this.userRequestRetryVersionConflictsInterceptorEnabled = userRequestRetryVersionConflictsInterceptorEnabled; } + public boolean getEnable_index_of_type() { + return enable_index_of_type; + } + + public void setEnable_index_of_type(boolean enable_index_of_type) { + this.enable_index_of_type = enable_index_of_type; + } + + public Boolean getResource_dbhistory_enabled() { + return resource_dbhistory_enabled; + } + + public void setResource_dbhistory_enabled(Boolean resource_dbhistory_enabled) { + this.resource_dbhistory_enabled = resource_dbhistory_enabled; + } + public static class Cors { private Boolean allow_Credentials = true; private List allowed_origin = List.of("*"); @@ -800,6 +807,38 @@ public class AppProperties { private Boolean partitioning_include_in_search_hashes = false; private Boolean allow_references_across_partitions = false; private Boolean conditional_create_duplicate_identifiers_enabled = false; + private Boolean database_partition_mode_enabled = false; + private Boolean patient_id_partitioning_mode = false; + private Integer default_partition_id = 0; + private boolean request_tenant_partitioning_mode; + + public boolean isRequest_tenant_partitioning_mode() { + return request_tenant_partitioning_mode; + } + + public Integer getDefault_partition_id() { + return default_partition_id; + } + + public void setDefault_partition_id(Integer theDefault_partition_id) { + default_partition_id = theDefault_partition_id; + } + + public Boolean getDatabase_partition_mode_enabled() { + return database_partition_mode_enabled; + } + + public void setDatabase_partition_mode_enabled(Boolean theDatabase_partition_mode_enabled) { + database_partition_mode_enabled = theDatabase_partition_mode_enabled; + } + + public Boolean getPatient_id_partitioning_mode() { + return patient_id_partitioning_mode; + } + + public void setPatient_id_partitioning_mode(Boolean thePatient_id_partitioning_mode) { + patient_id_partitioning_mode = thePatient_id_partitioning_mode; + } public Boolean getPartitioning_include_in_search_hashes() { return partitioning_include_in_search_hashes; @@ -825,10 +864,22 @@ public class AppProperties { Boolean conditional_create_duplicate_identifiers_enabled) { this.conditional_create_duplicate_identifiers_enabled = conditional_create_duplicate_identifiers_enabled; } + + public boolean getRequest_tenant_partitioning_mode() { + return request_tenant_partitioning_mode; + } + + public void setRequest_tenant_partitioning_mode(boolean theRequest_tenant_partitioning_mode) { + request_tenant_partitioning_mode = theRequest_tenant_partitioning_mode; + } } public static class Subscription { + private Boolean resthook_enabled = false; + private Boolean websocket_enabled = false; + private Email email = null; + public Boolean getResthook_enabled() { return resthook_enabled; } @@ -845,10 +896,6 @@ public class AppProperties { this.websocket_enabled = websocket_enabled; } - private Boolean resthook_enabled = false; - private Boolean websocket_enabled = false; - private Email email = null; - public Email getEmail() { return email; } @@ -858,6 +905,16 @@ public class AppProperties { } public static class Email { + private String from; + private String host; + private Integer port = 25; + private String username; + private String password; + private Boolean auth = false; + private Boolean startTlsEnable = false; + private Boolean startTlsRequired = false; + private Boolean quitWait = false; + public String getFrom() { return from; } @@ -929,32 +986,6 @@ public class AppProperties { public void setQuitWait(Boolean quitWait) { this.quitWait = quitWait; } - - private String from; - private String host; - private Integer port = 25; - private String username; - private String password; - private Boolean auth = false; - private Boolean startTlsEnable = false; - private Boolean startTlsRequired = false; - private Boolean quitWait = false; } } - - public boolean getEnable_index_of_type() { - return enable_index_of_type; - } - - public void setEnable_index_of_type(boolean enable_index_of_type) { - this.enable_index_of_type = enable_index_of_type; - } - - public Boolean getResource_dbhistory_enabled() { - return resource_dbhistory_enabled; - } - - public void setResource_dbhistory_enabled(Boolean resource_dbhistory_enabled) { - this.resource_dbhistory_enabled = resource_dbhistory_enabled; - } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksRequest.java b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksRequest.java index 088eab2..b50ae43 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksRequest.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksRequest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.jpa.starter.cdshooks; -import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson; +import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties({"extension"}) diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksServlet.java b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksServlet.java index 2d72e69..62a6b65 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksServlet.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksServlet.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jpa.starter.cdshooks; import ca.uhn.fhir.jpa.starter.AppProperties; +import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry; -import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson; import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson; import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ModuleConfigurationPrefetchSvc.java b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ModuleConfigurationPrefetchSvc.java index d2f4f2f..12e5594 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ModuleConfigurationPrefetchSvc.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ModuleConfigurationPrefetchSvc.java @@ -2,6 +2,9 @@ package ca.uhn.fhir.jpa.starter.cdshooks; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestAuthorizationJson; +import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor; @@ -11,8 +14,6 @@ import ca.uhn.fhir.util.UrlUtil; import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc; import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceMethod; import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson; -import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestAuthorizationJson; -import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson; import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchDaoSvc; import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchFhirClientSvc; import ca.uhn.hapi.fhir.cdshooks.svc.prefetch.CdsPrefetchSvc; @@ -72,12 +73,14 @@ public class ModuleConfigurationPrefetchSvc extends CdsPrefetchSvc { CdsResolutionStrategySvc theCdsResolutionStrategySvc, CdsPrefetchDaoSvc theResourcePrefetchDao, CdsPrefetchFhirClientSvc theResourcePrefetchFhirClient, - ICdsHooksDaoAuthorizationSvc theCdsHooksDaoAuthorizationSvc) { + ICdsHooksDaoAuthorizationSvc theCdsHooksDaoAuthorizationSvc, + IInterceptorBroadcaster theInterceptorBroadcaster) { super( theCdsResolutionStrategySvc, theResourcePrefetchDao, theResourcePrefetchFhirClient, - theCdsHooksDaoAuthorizationSvc); + theCdsHooksDaoAuthorizationSvc, + theInterceptorBroadcaster); myResourcePrefetchFhirClient = theResourcePrefetchFhirClient; fhirContext = theResourcePrefetchDao.getFhirContext(); } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCdsCrServiceR4.java b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCdsCrServiceR4.java index 51b31dc..249d477 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCdsCrServiceR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCdsCrServiceR4.java @@ -1,8 +1,8 @@ package ca.uhn.fhir.jpa.starter.cdshooks; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService; -import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson; import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrServiceR4; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Parameters; 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 e27517d..f77d3b2 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 @@ -27,6 +27,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import java.util.HashSet; import java.util.stream.Collectors; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + /** * This is the primary configuration file for the example server */ @@ -168,7 +170,7 @@ public class FhirServerConfigCommon { jpaStorageSettings.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000); jpaStorageSettings.setFilterParameterEnabled(appProperties.getFilter_search_enabled()); - jpaStorageSettings.setAdvancedHSearchIndexing(appProperties.getAdvanced_lucene_indexing()); + jpaStorageSettings.setHibernateSearchIndexSearchParams(appProperties.getAdvanced_lucene_indexing()); jpaStorageSettings.setTreatBaseUrlsAsLocal(new HashSet<>(appProperties.getLocal_base_urls())); jpaStorageSettings.setTreatReferencesAsLogical(new HashSet<>(appProperties.getLogical_urls())); @@ -237,6 +239,14 @@ public class FhirServerConfigCommon { // Partitioning if (appProperties.getPartitioning() != null) { retVal.setPartitioningEnabled(true); + boolean databasePartitionModeEnabled = + defaultIfNull(appProperties.getPartitioning().getDatabase_partition_mode_enabled(), Boolean.FALSE); + Integer defaultPartitionId = appProperties.getPartitioning().getDefault_partition_id(); + if (databasePartitionModeEnabled) { + retVal.setDatabasePartitionMode(true); + defaultPartitionId = defaultIfNull(defaultPartitionId, 0); + } + retVal.setDefaultPartitionId(defaultPartitionId); retVal.setIncludePartitionInSearchHashes( appProperties.getPartitioning().getPartitioning_include_in_search_hashes()); if (appProperties.getPartitioning().getAllow_references_across_partitions()) { @@ -251,6 +261,11 @@ public class FhirServerConfigCommon { return retVal; } + @Bean + public PartitionModeConfigurer partitionModeConfigurer() { + return new PartitionModeConfigurer(); + } + @Primary @Bean public HibernatePropertiesProvider jpaStarterDialectProvider( diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/PartitionModeConfigurer.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/PartitionModeConfigurer.java new file mode 100644 index 0000000..c83e8fc --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/PartitionModeConfigurer.java @@ -0,0 +1,56 @@ +package ca.uhn.fhir.jpa.starter.common; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; +import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; +import ca.uhn.fhir.jpa.starter.AppProperties; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; +import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class PartitionModeConfigurer { + private static final Logger ourLog = LoggerFactory.getLogger(PartitionModeConfigurer.class); + + @Autowired + private AppProperties myAppProperties; + + @Autowired + private FhirContext myFhirContext; + + @Autowired + private ISearchParamExtractor mySearchParamExtractor; + + @Autowired + private PartitionSettings myPartitionSettings; + + @Autowired + private RestfulServer myRestfulServer; + + @Autowired + private PartitionManagementProvider myPartitionManagementProvider; + + @PostConstruct + public void start() { + if (myAppProperties.getPartitioning() != null) { + if (myAppProperties.getPartitioning().getPatient_id_partitioning_mode() == Boolean.TRUE) { + ourLog.info("Partitioning mode enabled in: Patient ID partitioning mode"); + PatientIdPartitionInterceptor patientIdInterceptor = + new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings); + myRestfulServer.registerInterceptor(patientIdInterceptor); + myPartitionSettings.setUnnamedPartitionMode(true); + } else if (myAppProperties.getPartitioning().getRequest_tenant_partitioning_mode() == Boolean.TRUE) { + ourLog.info("Partitioning mode enabled in: Request tenant partitioning mode"); + myRestfulServer.registerInterceptor(new RequestTenantPartitionInterceptor()); + myRestfulServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); + } + + myRestfulServer.registerProviders(myPartitionManagementProvider); + } + } +} 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 8824c77..167dc77 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 @@ -18,7 +18,6 @@ import ca.uhn.fhir.jpa.binary.interceptor.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider; import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil; import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil; -import ca.uhn.fhir.jpa.config.util.ValidationSupportConfigUtil; import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.search.HSearchSortHelperImpl; @@ -32,9 +31,14 @@ import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; import ca.uhn.fhir.jpa.model.config.SubscriptionSettings; import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; -import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; +import ca.uhn.fhir.jpa.provider.DaoRegistryResourceSupportedSvc; +import ca.uhn.fhir.jpa.provider.IJpaSystemProvider; +import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; +import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; +import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; +import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; +import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.provider.*; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; @@ -46,23 +50,29 @@ import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider; import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper; import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; import ca.uhn.fhir.mdm.provider.MdmProviderLoader; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative2.NullNarrativeGenerator; import ca.uhn.fhir.rest.api.IResourceSupportedSvc; import ca.uhn.fhir.rest.openapi.OpenApiInterceptor; -import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; -import ca.uhn.fhir.rest.server.*; -import ca.uhn.fhir.rest.server.interceptor.*; +import ca.uhn.fhir.rest.server.ApacheProxyAddressStrategy; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IServerConformanceProvider; +import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; -import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; 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; @@ -70,14 +80,23 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.http.HttpHeaders; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.web.cors.CorsConfiguration; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; import javax.sql.DataSource; import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; @@ -100,12 +119,6 @@ public class StarterJpaConfig { return new StaleSearchDeletingSvcImpl(); } - @Primary - @Bean - public CachingValidationSupport validationSupportChain(JpaValidationSupportChain theJpaValidationSupportChain) { - return ValidationSupportConfigUtil.newCachingValidationSupport(theJpaValidationSupportChain); - } - @Autowired private ConfigurableEnvironment configurableEnvironment; @@ -271,7 +284,6 @@ public class StarterJpaConfig { BulkDataImportProvider bulkDataImportProvider, ValueSetOperationProvider theValueSetOperationProvider, ReindexProvider reindexProvider, - PartitionManagementProvider partitionManagementProvider, Optional repositoryValidatingInterceptor, IPackageInstallerSvc packageInstallerSvc, ThreadSafeResourceDeleterSvc theThreadSafeResourceDeleterSvc, @@ -443,12 +455,7 @@ public class StarterJpaConfig { // reindex Provider $reindex fhirServer.registerProvider(reindexProvider); - // Partitioning - if (appProperties.getPartitioning() != null) { - fhirServer.registerInterceptor(new RequestTenantPartitionInterceptor()); - fhirServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); - fhirServer.registerProviders(partitionManagementProvider); - } + // Validation repositoryValidatingInterceptor.ifPresent(fhirServer::registerInterceptor); // register custom interceptors diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDbpmR5IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDbpmR5IT.java new file mode 100644 index 0000000..2f6afa2 --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDbpmR5IT.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.migrate.JdbcUtils; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r5.model.Patient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = + { + "spring.datasource.url=jdbc:h2:mem:dbr5_dbpm", + "hapi.fhir.fhir_version=r5", + "hapi.fhir.partitioning.database_partition_mode_enabled=true", + "hapi.fhir.partitioning.patient_id_partitioning_mode=true" + }) +public class ExampleServerDbpmR5IT { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private IGenericClient ourClient; + private FhirContext ourCtx; + + + @LocalServerPort + private int port; + + @Autowired + private DataSource myDataSource; + + @Autowired + private PlatformTransactionManager myTxManager; + + + @Test + void testCreateAndRead() { + Patient pt = new Patient(); + pt.setId("A"); + pt.setActive(true); + IIdType id = ourClient.update().resource(pt).execute().getId(); + + Patient pt2 = ourClient.read().resource(Patient.class).withId("Patient/A").execute(); + assertTrue(pt2.getActive()); + } + + + @Test + public void testValidateSchema() throws SQLException { + TransactionTemplate tt = new TransactionTemplate(myTxManager); + DriverTypeEnum.ConnectionProperties cp = new DriverTypeEnum.ConnectionProperties(myDataSource, tt, DriverTypeEnum.H2_EMBEDDED); + Set columns = JdbcUtils.getPrimaryKeyColumns(cp, "HFJ_RESOURCE"); + assertThat(columns).containsExactlyInAnyOrder("RES_ID", "PARTITION_ID"); + } + + + @BeforeEach + void beforeEach() { + + ourCtx = FhirContext.forR5(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/fhir/"; + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } +} 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 7c48fdf..b33dc74 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -118,7 +118,7 @@ class ExampleServerR4IT implements IServerSupport { Parameters inParams = new Parameters(); inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01")); inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31")); - inParams.addParameter().setName("reportType").setValue(new StringType("summary")); + inParams.addParameter().setName("reportType").setValue(new StringType("population")); Parameters outParams = ourClient .operation() diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java index bbe1348..9233961 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java @@ -7,7 +7,11 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor; import ca.uhn.fhir.rest.server.provider.ProviderConstants; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -19,89 +23,86 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = - { - "spring.datasource.url=jdbc:h2:mem:dbr4-mt", - "hapi.fhir.fhir_version=r4", - "hapi.fhir.subscription.websocket_enabled=true", - "hapi.fhir.cr_enabled=false", - "hapi.fhir.partitioning.partitioning_include_in_search_hashes=false", - - }) + { + "spring.datasource.url=jdbc:h2:mem:dbr4-mt", + "hapi.fhir.fhir_version=r4", + "hapi.fhir.subscription.websocket_enabled=true", + "hapi.fhir.cr_enabled=false", + "hapi.fhir.partitioning.partitioning_include_in_search_hashes=false", + "hapi.fhir.partitioning.request_tenant_partitioning_mode=true", + }) class MultitenantServerR4IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); - private IGenericClient ourClient; - private FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private static UrlTenantSelectionInterceptor ourClientTenantInterceptor; + private IGenericClient ourClient; + private FhirContext ourCtx; + @LocalServerPort + private int port; - @LocalServerPort - private int port; - - private static UrlTenantSelectionInterceptor ourClientTenantInterceptor; + @Test + void testCreateAndReadInTenantA() { - @Test - void testCreateAndReadInTenantA() { + // Create tenant A + ourClientTenantInterceptor.setTenantId("DEFAULT"); + ourClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) + .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1)) + .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A")) + .execute(); - // Create tenant A - ourClientTenantInterceptor.setTenantId("DEFAULT"); - ourClient - .operation() - .onServer() - .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) - .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1)) - .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A")) - .execute(); + ourClientTenantInterceptor.setTenantId("TENANT-A"); + Patient pt = new Patient(); + pt.addName().setFamily("Family A"); + ourClient.create().resource(pt).execute().getId(); + + Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); + assertEquals(1, searchResult.getEntry().size()); + Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); + assertEquals("Family A", pt2.getName().get(0).getFamily()); + } + + @Test + void testCreateAndReadInTenantB() { - ourClientTenantInterceptor.setTenantId("TENANT-A"); - Patient pt = new Patient(); - pt.addName().setFamily("Family A"); - ourClient.create().resource(pt).execute().getId(); - - Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); - assertEquals(1, searchResult.getEntry().size()); - Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); - assertEquals("Family A", pt2.getName().get(0).getFamily()); - } - - @Test - void testCreateAndReadInTenantB() { + // Create tenant A + ourClientTenantInterceptor.setTenantId("DEFAULT"); + ourClient + .operation() + .onServer() + .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) + .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2)) + .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B")) + .execute(); - // Create tenant A - ourClientTenantInterceptor.setTenantId("DEFAULT"); - ourClient - .operation() - .onServer() - .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) - .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2)) - .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B")) - .execute(); + ourClientTenantInterceptor.setTenantId("TENANT-B"); + Patient pt = new Patient(); + pt.addName().setFamily("Family B"); + ourClient.create().resource(pt).execute().getId(); + Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); + assertEquals(1, searchResult.getEntry().size()); + Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); + assertEquals("Family B", pt2.getName().get(0).getFamily()); + } - ourClientTenantInterceptor.setTenantId("TENANT-B"); - Patient pt = new Patient(); - pt.addName().setFamily("Family B"); - ourClient.create().resource(pt).execute().getId(); + @BeforeEach + void beforeEach() { - Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); - assertEquals(1, searchResult.getEntry().size()); - Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); - assertEquals("Family B", pt2.getName().get(0).getFamily()); - } - - @BeforeEach - void beforeEach() { - - ourClientTenantInterceptor = new UrlTenantSelectionInterceptor(); - ourCtx = FhirContext.forR4(); - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = "http://localhost:" + port + "/fhir/"; - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); - ourClient.registerInterceptor(ourClientTenantInterceptor); - } + ourClientTenantInterceptor = new UrlTenantSelectionInterceptor(); + ourCtx = FhirContext.forR4(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/fhir/"; + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + ourClient.registerInterceptor(ourClientTenantInterceptor); + } } diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index a494359..f57c1e2 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -145,10 +145,27 @@ hapi: # local_base_urls: # - https://hapi.fhir.org/baseR4 mdm_enabled: false + + ### Uncomment the following section, and any sub-properties you need in order to enable + ### partitioning support on this server. # partitioning: # allow_references_across_partitions: false # partitioning_include_in_search_hashes: false - # partitioning_include_in_search_hashes + # default_partition_id: 0 + # ### Enable the following setting to enable Database Partitioning Mode + # ### See: https://hapifhir.io/hapi-fhir/docs/server_jpa_partitioning/db_partition_mode.html + # database_partition_mode_enabled: false + # ### Partition Style: Partitioning requires a partition interceptor which helps the server + # ### select which partition(s) should be accessed for a given request. You can supply your + # ### own interceptor (see https://hapifhir.io/hapi-fhir/docs/server_jpa_partitioning/partitioning.html#partition-interceptors ) + # ### but the following setting can also be used to use a built-in form. + # ### Patient ID Partitioning Mode uses the patient/subject ID to determine the partition + # patient_id_partitioning_mode: false + # ### Request tenant mode can be used for a multi-tenancy setup where the request path is + # ### expected to have an additional path element, e.g. GET http://example.com/fhir/TENANT-ID/Patient/A + # request_tenant_partitioning_mode: 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-