Merge pull request #758 from hapifhir/rel_7_8_tracking

Update to HAPI 8.0.0
This commit is contained in:
dotasek
2025-02-24 09:44:21 -05:00
committed by GitHub
13 changed files with 368 additions and 154 deletions

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.6.0</version>
<version>8.0.0</version>
</parent>
<artifactId>hapi-fhir-jpaserver-starter</artifactId>

View File

@@ -23,6 +23,11 @@ import java.util.Set;
@EnableConfigurationProperties
public class AppProperties {
private final Set<String> auto_version_reference_at_paths = new HashSet<>();
private final Set<String> local_base_urls = new HashSet<>();
private final Set<String> logical_urls = new HashSet<>();
private final List<String> custom_interceptor_classes = new ArrayList<>();
private final List<String> 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<String> 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<String> supported_resource_types = new ArrayList<>();
private List<Bundle.BundleType> allowed_bundle_types = null;
private Boolean narrative_enabled = true;
private Boolean ig_runtime_upload_enabled = false;
private Validation validation = new Validation();
private Map<String, Tester> 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<String, PackageInstallationSpec> 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<String> local_base_urls = new HashSet<>();
private final Set<String> logical_urls = new HashSet<>();
private Boolean resource_dbhistory_enabled = true;
private final List<String> custom_interceptor_classes = new ArrayList<>();
private final List<String> 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<String> 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;
}
}

View File

@@ -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"})

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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(

View File

@@ -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);
}
}
}

View File

@@ -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> 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

View File

@@ -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<String> 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));
}
}

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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-