Merged the rel_5_3_0 branch into this Branch to pick up the MDM changes.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
[*.java]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
indent_size = 3
|
||||
|
||||
|
||||
@@ -320,6 +320,10 @@ The server may be configured with subscription support by enabling properties in
|
||||
|
||||
Set `hapi.fhir.cql_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable [Clinical Quality Language](https://cql.hl7.org/) on this server.
|
||||
|
||||
## Enabling MDM (EMPI)
|
||||
|
||||
Set `hapi.fhir.mdm_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable MDM on this server. The MDM matching rules are configured in [mdm-rules.json](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/mdm-rules.json). The rules in this example file should be replaced with actual matching rules appropriate to your data. Note that MDM relies on subscriptions, so for MDM to work, subscriptions must be enabled.
|
||||
|
||||
## Using Elasticsearch
|
||||
|
||||
By default, the server will use embedded lucene indexes for terminology and fulltext indexing purposes. You can switch this to using lucene by editing the properties in [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml)
|
||||
@@ -328,9 +332,10 @@ For example:
|
||||
|
||||
```properties
|
||||
elasticsearch.enabled=true
|
||||
elasticsearch.rest_url=http://localhost:9200
|
||||
elasticsearch.rest_url=localhost:9200
|
||||
elasticsearch.username=SomeUsername
|
||||
elasticsearch.password=SomePassword
|
||||
elasticsearch.protocol=http
|
||||
elasticsearch.required_index_status=YELLOW
|
||||
elasticsearch.schema_management_strategy=CREATE
|
||||
```
|
||||
|
||||
23
pom.xml
23
pom.xml
@@ -22,7 +22,6 @@
|
||||
|
||||
<properties>
|
||||
<java.version>8</java.version>
|
||||
<spring_boot_version>2.3.4.RELEASE</spring_boot_version>
|
||||
</properties>
|
||||
|
||||
<prerequisites>
|
||||
@@ -107,6 +106,12 @@
|
||||
<artifactId>hapi-fhir-jpaserver-cql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- This dependency includes the JPA MDM Server -->
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-mdm</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- This dependency is used for the "FHIR Tester" web app overlay -->
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
@@ -235,6 +240,21 @@
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>elasticsearch</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--
|
||||
@@ -358,7 +378,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
|
||||
45
src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java
Normal file
45
src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package ca.uhn.fhir.jpa.mdm;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.mdm.config.MdmConsumerConfig;
|
||||
import ca.uhn.fhir.jpa.mdm.config.MdmSubmitterConfig;
|
||||
import ca.uhn.fhir.jpa.starter.AppProperties;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* TODO: Move this to package "ca.uhn.fhir.jpa.starter" in HAPI FHIR 5.2.0+. The lousy component scan
|
||||
* in 5.1.0 picks this up even if MDM is disabled currently.
|
||||
*/
|
||||
@Configuration
|
||||
@Conditional(MdmConfigCondition.class)
|
||||
@Import({MdmConsumerConfig.class, MdmSubmitterConfig.class})
|
||||
public class MdmConfig {
|
||||
|
||||
@Bean
|
||||
MdmRuleValidator mdmRuleValidator(FhirContext theFhirContext, ISearchParamRetriever theSearchParamRetriever) {
|
||||
return new MdmRuleValidator(theFhirContext, theSearchParamRetriever);
|
||||
}
|
||||
|
||||
@Bean
|
||||
IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties) throws IOException {
|
||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
Resource resource = resourceLoader.getResource("mdm-rules.json");
|
||||
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
|
||||
return new MdmSettings(theMdmRuleValidator).setEnabled(appProperties.getMdm_enabled()).setScriptText(json);
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfigCondition.java
Normal file
13
src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfigCondition.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package ca.uhn.fhir.jpa.mdm;
|
||||
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
public class MdmConfigCondition implements Condition {
|
||||
@Override
|
||||
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
|
||||
String property = conditionContext.getEnvironment().getProperty("hapi.fhir.mdm_enabled");
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
public class AppProperties {
|
||||
|
||||
private Boolean cql_enabled = false;
|
||||
private Boolean mdm_enabled = false;
|
||||
private Boolean allow_cascading_deletes = false;
|
||||
private Boolean allow_contains_searches = true;
|
||||
private Boolean allow_external_references = false;
|
||||
@@ -31,6 +32,7 @@ public class AppProperties {
|
||||
private Boolean allow_placeholder_references = true;
|
||||
private Boolean auto_create_placeholder_reference_targets = true;
|
||||
private Boolean enable_index_missing_fields = false;
|
||||
private Boolean enable_repository_validating_interceptor = false;
|
||||
private Boolean enforce_referential_integrity_on_delete = true;
|
||||
private Boolean enforce_referential_integrity_on_write = true;
|
||||
private Boolean etag_support_enabled = true;
|
||||
@@ -97,6 +99,14 @@ public class AppProperties {
|
||||
this.cql_enabled = cql_enabled;
|
||||
}
|
||||
|
||||
public Boolean getMdm_enabled() {
|
||||
return mdm_enabled;
|
||||
}
|
||||
|
||||
public void setMdm_enabled(Boolean mdm_enabled) {
|
||||
this.mdm_enabled = mdm_enabled;
|
||||
}
|
||||
|
||||
public Cors getCors() {
|
||||
return cors;
|
||||
}
|
||||
@@ -244,7 +254,15 @@ public class AppProperties {
|
||||
this.enable_index_missing_fields = enable_index_missing_fields;
|
||||
}
|
||||
|
||||
public Boolean getEnforce_referential_integrity_on_delete() {
|
||||
public Boolean getEnable_repository_validating_interceptor() {
|
||||
return enable_repository_validating_interceptor;
|
||||
}
|
||||
|
||||
public void setEnable_repository_validating_interceptor(Boolean theEnable_repository_validating_interceptor) {
|
||||
enable_repository_validating_interceptor = theEnable_repository_validating_interceptor;
|
||||
}
|
||||
|
||||
public Boolean getEnforce_referential_integrity_on_delete() {
|
||||
return enforce_referential_integrity_on_delete;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ca.uhn.fhir.jpa.starter;
|
||||
|
||||
import ca.uhn.fhir.jpa.mdm.MdmConfig;
|
||||
import ca.uhn.fhir.jpa.starter.annotations.OnEitherVersion;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
|
||||
@@ -23,7 +24,7 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
@ServletComponentScan(basePackageClasses = {
|
||||
JpaRestfulServer.class})
|
||||
@SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class})
|
||||
@Import({SubscriptionSubmitterConfig.class, SubscriptionProcessorConfig.class, SubscriptionChannelConfig.class, WebsocketDispatcherConfig.class})
|
||||
@Import({SubscriptionSubmitterConfig.class, SubscriptionProcessorConfig.class, SubscriptionChannelConfig.class, WebsocketDispatcherConfig.class, MdmConfig.class})
|
||||
public class Application extends SpringBootServletInitializer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
||||
import ca.uhn.fhir.jpa.bulk.provider.BulkDataExportProvider;
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
|
||||
import ca.uhn.fhir.jpa.packages.PackageInstallationSpec;
|
||||
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
||||
@@ -365,6 +366,12 @@ public class BaseJpaRestfulServer extends RestfulServer {
|
||||
daoConfig.setLastNEnabled(true);
|
||||
}
|
||||
|
||||
// Repository Validating Interceptor
|
||||
if (Boolean.TRUE.equals(appProperties.getEnable_repository_validating_interceptor())) {
|
||||
RepositoryValidationInterceptorFactory repositoryValidationInterceptorFactory = myApplicationContext.getBean(RepositoryValidationInterceptorFactory.class);
|
||||
RepositoryValidatingInterceptor interceptor = repositoryValidationInterceptorFactory.build();
|
||||
interceptorService.registerInterceptor(interceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package ca.uhn.fhir.jpa.starter;
|
||||
|
||||
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
|
||||
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
|
||||
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
|
||||
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.elasticsearch.action.admin.indices.stats.IndexStats;
|
||||
import org.hibernate.search.backend.elasticsearch.index.IndexStatus;
|
||||
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
|
||||
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
|
||||
import org.hibernate.search.engine.cfg.BackendSettings;
|
||||
import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames;
|
||||
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
|
||||
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
@@ -19,7 +27,6 @@ public class EnvironmentHelper {
|
||||
Properties properties = new Properties();
|
||||
|
||||
if (environment.getProperty("spring.jpa.properties", String.class) == null) {
|
||||
properties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory");
|
||||
properties.put("hibernate.format_sql", "false");
|
||||
properties.put("hibernate.show_sql", "false");
|
||||
properties.put("hibernate.hbm2ddl.auto", "update");
|
||||
@@ -28,11 +35,18 @@ public class EnvironmentHelper {
|
||||
properties.put("hibernate.cache.use_second_level_cache", "false");
|
||||
properties.put("hibernate.cache.use_structured_entries", "false");
|
||||
properties.put("hibernate.cache.use_minimal_puts", "false");
|
||||
properties.put("hibernate.search.default.directory_provider", "filesystem");
|
||||
properties.put("hibernate.search.default.indexBase", "target/lucenefiles");
|
||||
properties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
|
||||
|
||||
properties.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE), "local-filesystem");
|
||||
properties.put(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_ROOT), "target/lucenefiles");
|
||||
properties.put(BackendSettings.backendKey(BackendSettings.TYPE), "lucene");
|
||||
properties.put(BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER), HapiLuceneAnalysisConfigurer.class.getName());
|
||||
properties.put(BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), "LUCENE_CURRENT");
|
||||
|
||||
//Set this value to true in the properties to enable lucene.
|
||||
properties.put(HibernateOrmMapperSettings.ENABLED, environment.getProperty("spring.jpa.properties.hibernate.search.enabled", "false"));
|
||||
|
||||
} else {
|
||||
Arrays.asList(environment.getProperty("spring.jpa.properties", String.class).split(" ")).stream().forEach(s ->
|
||||
Arrays.asList(environment.getProperty("spring.jpa.properties", String.class).split(" ")).stream().filter(s -> !StringUtils.isEmpty(s)).forEach(s ->
|
||||
{
|
||||
String[] values = s.split("=");
|
||||
properties.put(values[0], values[1]);
|
||||
@@ -42,9 +56,9 @@ public class EnvironmentHelper {
|
||||
if (environment.getProperty("elasticsearch.enabled", Boolean.class) != null
|
||||
&& environment.getProperty("elasticsearch.enabled", Boolean.class) == true ){
|
||||
ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder();
|
||||
ElasticsearchIndexStatus requiredIndexStatus = environment.getProperty("elasticsearch.required_index_status", ElasticsearchIndexStatus.class);
|
||||
IndexStatus requiredIndexStatus = environment.getProperty("elasticsearch.required_index_status", IndexStatus.class);
|
||||
if (requiredIndexStatus == null) {
|
||||
builder.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW);
|
||||
builder.setRequiredIndexStatus(IndexStatus.YELLOW);
|
||||
} else {
|
||||
builder.setRequiredIndexStatus(requiredIndexStatus);
|
||||
}
|
||||
@@ -52,18 +66,19 @@ public class EnvironmentHelper {
|
||||
builder.setRestUrl(getElasticsearchServerUrl(environment));
|
||||
builder.setUsername(getElasticsearchServerUsername(environment));
|
||||
builder.setPassword(getElasticsearchServerPassword(environment));
|
||||
IndexSchemaManagementStrategy indexSchemaManagementStrategy = environment.getProperty("elasticsearch.schema_management_strategy", IndexSchemaManagementStrategy.class);
|
||||
builder.setProtocol(getElasticsearchServerProtocol(environment));
|
||||
SchemaManagementStrategyName indexSchemaManagementStrategy = environment.getProperty("elasticsearch.schema_management_strategy", SchemaManagementStrategyName.class);
|
||||
if (indexSchemaManagementStrategy == null) {
|
||||
builder.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE);
|
||||
builder.setIndexSchemaManagementStrategy(SchemaManagementStrategyName.CREATE);
|
||||
} else {
|
||||
builder.setIndexSchemaManagementStrategy(indexSchemaManagementStrategy);
|
||||
}
|
||||
// pretty_print_json_log: false
|
||||
Boolean refreshAfterWrite = environment.getProperty("elasticsearch.debug.refresh_after_write", Boolean.class);
|
||||
if (refreshAfterWrite == null) {
|
||||
builder.setDebugRefreshAfterWrite(false);
|
||||
if (refreshAfterWrite == null || refreshAfterWrite == false) {
|
||||
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.ASYNC);
|
||||
} else {
|
||||
builder.setDebugRefreshAfterWrite(refreshAfterWrite);
|
||||
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.READ_SYNC);
|
||||
}
|
||||
// pretty_print_json_log: false
|
||||
Boolean prettyPrintJsonLog = environment.getProperty("elasticsearch.debug.pretty_print_json_log", Boolean.class);
|
||||
@@ -74,7 +89,6 @@ public class EnvironmentHelper {
|
||||
}
|
||||
builder.apply(properties);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -82,7 +96,11 @@ public class EnvironmentHelper {
|
||||
return environment.getProperty("elasticsearch.rest_url", String.class);
|
||||
}
|
||||
|
||||
public static String getElasticsearchServerUsername(ConfigurableEnvironment environment) {
|
||||
public static String getElasticsearchServerProtocol(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("elasticsearch.protocol", String.class, "http");
|
||||
}
|
||||
|
||||
public static String getElasticsearchServerUsername(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("elasticsearch.username");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.starter;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.binstore.DatabaseBlobBinaryStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
|
||||
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
|
||||
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory;
|
||||
@@ -132,10 +132,17 @@ public class FhirServerConfigCommon {
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RepositoryValidationInterceptorFactory repositoryValidationInterceptorFactory() {
|
||||
return new RepositoryValidationInterceptorFactory();
|
||||
}
|
||||
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public HibernateDialectProvider jpaStarterDialectProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
|
||||
return new JpaHibernateDialectProvider(myEntityManagerFactory);
|
||||
public HibernatePropertiesProvider jpaStarterDialectProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
|
||||
return new JpaHibernatePropertiesProvider(myEntityManagerFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -69,8 +69,14 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 {
|
||||
@Bean()
|
||||
public ElasticsearchSvcImpl elasticsearchSvc() {
|
||||
if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) {
|
||||
String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment);
|
||||
String elasticsearchHost = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://")+3, elasticsearchUrl.lastIndexOf(":"));
|
||||
String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment);
|
||||
String elasticsearchHost;
|
||||
if (elasticsearchUrl.startsWith("http")) {
|
||||
elasticsearchHost = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://") + 3, elasticsearchUrl.lastIndexOf(":"));
|
||||
} else {
|
||||
elasticsearchHost = elasticsearchUrl.substring(0, elasticsearchUrl.indexOf(":"));
|
||||
}
|
||||
|
||||
String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment);
|
||||
String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment);
|
||||
int elasticsearchPort = Integer.parseInt(elasticsearchUrl.substring(elasticsearchUrl.lastIndexOf(":")+1));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ca.uhn.fhir.jpa.starter;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.jpa.config.HibernateDialectProvider;
|
||||
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
|
||||
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
|
||||
@@ -10,11 +10,11 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class JpaHibernateDialectProvider extends HibernateDialectProvider {
|
||||
public class JpaHibernatePropertiesProvider extends HibernatePropertiesProvider {
|
||||
|
||||
private final Dialect dialect;
|
||||
|
||||
public JpaHibernateDialectProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
|
||||
public JpaHibernatePropertiesProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
|
||||
DataSource connection = myEntityManagerFactory.getDataSource();
|
||||
try {
|
||||
dialect = new StandardDialectResolver()
|
||||
@@ -0,0 +1,38 @@
|
||||
package ca.uhn.fhir.jpa.starter;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule;
|
||||
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class can be customized to enable the {@link ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor}
|
||||
* on this server.
|
||||
*
|
||||
* The <code>enable_repository_validating_interceptor</code> property must be enabled in <code>application.yaml</code>
|
||||
* in order to use this class.
|
||||
*/
|
||||
public class RepositoryValidationInterceptorFactory {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext myApplicationContext;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
public RepositoryValidatingInterceptor build() {
|
||||
RepositoryValidatingRuleBuilder ruleBuilder = myApplicationContext.getBean(RepositoryValidatingRuleBuilder.class);
|
||||
|
||||
// Customize the ruleBuilder here to have the rules you want! We will give a simple example
|
||||
// of enabling validation for all Patient resources
|
||||
ruleBuilder.forResourcesOfType("Patient").requireValidationToDeclaredProfiles();
|
||||
|
||||
// Do not customize below this line
|
||||
List<IRepositoryValidatingRule> rules = ruleBuilder.build();
|
||||
return new RepositoryValidatingInterceptor(myFhirContext, rules);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ spring:
|
||||
# hibernate.cache.use_second_level_cache: false
|
||||
# hibernate.cache.use_structured_entries: false
|
||||
# hibernate.cache.use_minimal_puts: false
|
||||
# hibernate.search.enabled: true
|
||||
# hibernate.search.default.directory_provider: filesystem
|
||||
# hibernate.search.default.indexbase: target/lucenefiles
|
||||
# hibernate.search.lucene_version: lucene_current
|
||||
@@ -58,6 +59,7 @@ hapi:
|
||||
# default_encoding: JSON
|
||||
# default_pretty_print: true
|
||||
# default_page_size: 20
|
||||
# enable_repository_validating_interceptor: false
|
||||
# enable_index_missing_fields: false
|
||||
# enforce_referential_integrity_on_delete: false
|
||||
# enforce_referential_integrity_on_write: false
|
||||
@@ -135,6 +137,7 @@ hapi:
|
||||
# enabled: false
|
||||
# password: SomePassword
|
||||
# required_index_status: YELLOW
|
||||
# rest_url: 'http://localhost:9200'
|
||||
# rest_url: 'localhost:9200'
|
||||
# protocol: 'http'
|
||||
# schema_management_strategy: CREATE
|
||||
# username: SomeUsername
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"version": "1",
|
||||
"mdmTypes": ["Patient", "Practitioner"],
|
||||
"candidateSearchParams": [
|
||||
{
|
||||
"resourceType": "Patient",
|
||||
@@ -26,11 +26,13 @@ import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic;
|
||||
import pl.allegro.tech.embeddedelasticsearch.PopularProperties;
|
||||
import org.testcontainers.elasticsearch.ElasticsearchContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.UUID;
|
||||
@@ -49,7 +51,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
// Because the port is set randomly, we will set the rest_url using the Initializer.
|
||||
// "elasticsearch.rest_url='http://localhost:9200'",
|
||||
"elasticsearch.username=SomeUsername",
|
||||
"elasticsearch.password=SomePassword"
|
||||
"elasticsearch.password=SomePassword",
|
||||
"elasticsearch.protocol=http"
|
||||
})
|
||||
@ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class)
|
||||
public class ElasticsearchLastNR4IT {
|
||||
@@ -57,30 +60,20 @@ public class ElasticsearchLastNR4IT {
|
||||
private IGenericClient ourClient;
|
||||
private FhirContext ourCtx;
|
||||
|
||||
private static final String ELASTIC_VERSION = "6.5.4";
|
||||
private static EmbeddedElastic embeddedElastic;
|
||||
private static final String ELASTIC_VERSION = "7.10.0";
|
||||
private static final String ELASTIC_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:" + ELASTIC_VERSION;
|
||||
|
||||
private static ElasticsearchContainer embeddedElastic;
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchSvcImpl myElasticsearchSvc;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
|
||||
embeddedElastic = null;
|
||||
try {
|
||||
embeddedElastic = EmbeddedElastic.builder()
|
||||
.withElasticVersion(ELASTIC_VERSION)
|
||||
.withSetting(PopularProperties.TRANSPORT_TCP_PORT, 0)
|
||||
.withSetting(PopularProperties.HTTP_PORT, 0)
|
||||
.withSetting(PopularProperties.CLUSTER_NAME, UUID.randomUUID())
|
||||
.withStartTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
.start();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
embeddedElastic = new ElasticsearchContainer(ELASTIC_IMAGE).withStartupTimeout(Duration.of(300, ChronoUnit.SECONDS));
|
||||
embeddedElastic.start();
|
||||
}
|
||||
|
||||
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
embeddedElastic.stop();
|
||||
@@ -136,7 +129,7 @@ public class ElasticsearchLastNR4IT {
|
||||
public void initialize(
|
||||
ConfigurableApplicationContext configurableApplicationContext) {
|
||||
// Since the port is dynamically generated, replace the URL with one that has the correct port
|
||||
TestPropertyValues.of("elasticsearch.rest_url=http://localhost:" + embeddedElastic.getHttpPort())
|
||||
TestPropertyValues.of("elasticsearch.rest_url=" + embeddedElastic.getHost() +":" + embeddedElastic.getMappedPort(9200))
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,12 @@ import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -27,246 +29,145 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static ca.uhn.fhir.util.TestUtil.waitForSize;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
|
||||
{
|
||||
"spring.batch.job.enabled=false",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"hapi.fhir.fhir_version=R4",
|
||||
"hapi.fhir.cql_enabled=true",
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
//Override is currently required when using Empi as the construction of the Empi beans are ambiguous as they are constructed multiple places. This is evident when running in a spring boot environment
|
||||
"spring.main.allow-bean-definition-overriding=true"
|
||||
})
|
||||
public class ExampleServerR4IT implements IServerSupport {
|
||||
{
|
||||
"spring.batch.job.enabled=false",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
"hapi.fhir.mdm_enabled=true",
|
||||
//Override is currently required when using MDM as the construction of the MDM beans are ambiguous as they are constructed multiple places. This is evident when running in a spring boot environment
|
||||
"spring.main.allow-bean-definition-overriding=true"
|
||||
})
|
||||
public class ExampleServerR4IT {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
|
||||
private IGenericClient ourClient;
|
||||
private FhirContext ourCtx;
|
||||
private String ourServerBaseURL;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
|
||||
private IGenericClient ourClient;
|
||||
private FhirContext ourCtx;
|
||||
|
||||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
|
||||
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
|
||||
ourServerBaseURL = "http://localhost:" + port + "/fhir/";
|
||||
ourClient = ourCtx.newRestfulGenericClient(ourServerBaseURL);
|
||||
ourClient.registerInterceptor(new LoggingInterceptor(true));
|
||||
}
|
||||
@Test
|
||||
@Order(0)
|
||||
void testCreateAndRead() {
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
ourLog.info("Finished running a test...");
|
||||
}
|
||||
String methodName = "testCreateResourceConditional";
|
||||
|
||||
@Test
|
||||
public void testCQLEXM104EvaluateMeasure() throws IOException {
|
||||
String measureId = "measure-EXM104-8.2.000";
|
||||
Patient pt = new Patient();
|
||||
pt.setActive(true);
|
||||
pt.getBirthDateElement().setValueAsString("2020-01-01");
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("12345");
|
||||
pt.addName().setFamily(methodName);
|
||||
IIdType id = ourClient.create().resource(pt).execute().getId();
|
||||
|
||||
loadBundle("r4/EXM104/EXM104-8.2.000-bundle.json", ourCtx, ourClient);
|
||||
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
|
||||
assertEquals(methodName, pt2.getName().get(0).getFamily());
|
||||
|
||||
// http://localhost:8080/fhir/Measure/measure-EXM104-8.2.000/$evaluate-measure?periodStart=2019-01-01&periodEnd=2019-12-31
|
||||
Parameters inParams = new Parameters();
|
||||
inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01"));
|
||||
inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31"));
|
||||
// Test MDM
|
||||
|
||||
Parameters outParams = ourClient
|
||||
.operation()
|
||||
.onInstance(new IdDt("Measure", measureId))
|
||||
.named("$evaluate-measure")
|
||||
.withParameters(inParams)
|
||||
.cacheControl(new CacheControlDirective().setNoCache(true))
|
||||
.withAdditionalHeader("Content-Type", "application/json")
|
||||
.useHttpGet()
|
||||
.execute();
|
||||
// Wait until the MDM message has been processed
|
||||
await().until(() -> getPatients().size(), equalTo(2));
|
||||
List<Patient> persons = getPatients();
|
||||
Patient goldenRecord = persons.get(0);
|
||||
|
||||
List<Parameters.ParametersParameterComponent> response = outParams.getParameter();
|
||||
Assert.assertTrue(!response.isEmpty());
|
||||
Parameters.ParametersParameterComponent component = response.get(0);
|
||||
Assert.assertTrue(component.getResource() instanceof MeasureReport);
|
||||
MeasureReport report = (MeasureReport) component.getResource();
|
||||
Assert.assertEquals("Measure/"+measureId, report.getMeasure());
|
||||
}
|
||||
// Verify that a golden record Patient was created
|
||||
assertNotNull(goldenRecord.getMeta().getTag("http://hapifhir.io/fhir/NamingSystem/mdm-record-status", "GOLDEN_RECORD"));
|
||||
}
|
||||
|
||||
private Bundle loadBundle(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
|
||||
String json = stringFromResource(theLocation);
|
||||
Bundle bundle = (Bundle) theCtx.newJsonParser().parseResource(json);
|
||||
Bundle result = (Bundle) theClient.transaction().withBundle(bundle).execute();
|
||||
return result;
|
||||
}
|
||||
private List<Patient> getPatients() {
|
||||
Bundle bundle = ourClient.search().forResource(Patient.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute();
|
||||
List<Patient> retVal = BundleUtil.toListOfResourcesOfType(ourCtx, bundle, Patient.class);
|
||||
retVal.sort(comparing(o -> ((Patient) o).getMeta().getLastUpdated()).reversed());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCQLEXM130EvaluateMeasure() throws IOException {
|
||||
String measureId = "measure-EXM130-7.3.000";
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testWebsocketSubscription() throws Exception {
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
|
||||
subscription.setCriteria("Observation?status=final");
|
||||
|
||||
loadBundle("r4/EXM130/EXM130-7.3.000-bundle.json", ourCtx, ourClient);
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
// http://localhost:8080/fhir/Measure/measure-EXM130-FHIR4-7.2.000/$evaluate-measure?periodStart=2019-01-01&periodEnd=2019-12-31
|
||||
Parameters inParams = new Parameters();
|
||||
// inParams.addParameter().setName("measure").setValue(new StringType("Measure/measure-EXM104-8.2.000"));
|
||||
// inParams.addParameter().setName("patient").setValue(new StringType("Patient/numer-EXM104-FHIR3"));
|
||||
inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01"));
|
||||
inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31"));
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
IIdType mySubscriptionId = methodOutcome.getId();
|
||||
|
||||
Parameters outParams = ourClient
|
||||
.operation()
|
||||
.onInstance(new IdDt("Measure", measureId))
|
||||
.named("$evaluate-measure")
|
||||
.withParameters(inParams)
|
||||
.cacheControl(new CacheControlDirective().setNoCache(true))
|
||||
.withAdditionalHeader("Content-Type", "application/json")
|
||||
.useHttpGet()
|
||||
.execute();
|
||||
// Wait for the subscription to be activated
|
||||
await().until(() -> activeSubscriptionCount() == 3);
|
||||
|
||||
List<Parameters.ParametersParameterComponent> response = outParams.getParameter();
|
||||
Assert.assertTrue(!response.isEmpty());
|
||||
Parameters.ParametersParameterComponent component = response.get(0);
|
||||
Assert.assertTrue(component.getResource() instanceof MeasureReport);
|
||||
MeasureReport report = (MeasureReport) component.getResource();
|
||||
Assert.assertEquals("Measure/"+measureId, report.getMeasure());
|
||||
}
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testCQLEXM349EvaluateMeasure() throws IOException {
|
||||
String measureId = "measure-EXM349-2.10.000";
|
||||
WebSocketClient myWebSocketClient = new WebSocketClient();
|
||||
SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON);
|
||||
|
||||
loadBundle("r4/EXM349/EXM349-2.10.000-bundle.json.manuallyeditedtoremovesupplementaldata", ourCtx, ourClient);
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + port + "/websocket");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
// http://localhost:8080/fhir/Measure/measure-EXM349-2.10.000/$evaluate-measure?periodStart=2019-01-01&periodEnd=2019-12-31
|
||||
Parameters inParams = new Parameters();
|
||||
inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01"));
|
||||
inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31"));
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
|
||||
Parameters outParams = ourClient
|
||||
.operation()
|
||||
.onInstance(new IdDt("Measure", measureId))
|
||||
.named("$evaluate-measure")
|
||||
.withParameters(inParams)
|
||||
.cacheControl(new CacheControlDirective().setNoCache(true))
|
||||
.withAdditionalHeader("Content-Type", "application/json")
|
||||
.useHttpGet()
|
||||
.execute();
|
||||
/*
|
||||
* Create a matching resource
|
||||
*/
|
||||
Observation obs = new Observation();
|
||||
obs.setStatus(Observation.ObservationStatus.FINAL);
|
||||
ourClient.create().resource(obs).execute();
|
||||
|
||||
List<Parameters.ParametersParameterComponent> response = outParams.getParameter();
|
||||
Assert.assertTrue(!response.isEmpty());
|
||||
Parameters.ParametersParameterComponent component = response.get(0);
|
||||
Assert.assertTrue(component.getResource() instanceof MeasureReport);
|
||||
MeasureReport report = (MeasureReport) component.getResource();
|
||||
Assert.assertEquals("Measure/"+measureId, report.getMeasure());
|
||||
}
|
||||
// Give some time for the subscription to deliver
|
||||
Thread.sleep(2000);
|
||||
|
||||
//@Test
|
||||
void testCreateAndRead() {
|
||||
/*
|
||||
* Ensure that we receive a ping on the websocket
|
||||
*/
|
||||
waitForSize(1, () -> mySocketImplementation.myPingCount);
|
||||
|
||||
String methodName = "testCreateResourceConditional";
|
||||
/*
|
||||
* Clean up
|
||||
*/
|
||||
ourClient.delete().resourceById(mySubscriptionId).execute();
|
||||
}
|
||||
|
||||
Patient pt = new Patient();
|
||||
pt.setActive(true);
|
||||
pt.getBirthDateElement().setValueAsString("2020-01-01");
|
||||
pt.addIdentifier().setSystem("http://foo").setValue("12345");
|
||||
pt.addName().setFamily(methodName);
|
||||
IIdType id = ourClient.create().resource(pt).execute().getId();
|
||||
private int activeSubscriptionCount() {
|
||||
return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size();
|
||||
}
|
||||
|
||||
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
|
||||
assertEquals(methodName, pt2.getName().get(0).getFamily());
|
||||
|
||||
// Wait until the message has been processed
|
||||
await().until(() -> getPeople().size() > 0);
|
||||
List<Person> persons = getPeople();
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
|
||||
// Verify a Person was created that links to our Patient
|
||||
Optional<String> personLinkToCreatedPatient = persons.stream()
|
||||
.map(Person::getLink)
|
||||
.flatMap(Collection::stream)
|
||||
.map(Person.PersonLinkComponent::getTarget)
|
||||
.map(Reference::getReference)
|
||||
.filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid))
|
||||
.findAny();
|
||||
assertTrue(personLinkToCreatedPatient.isPresent());
|
||||
}
|
||||
|
||||
private List<Person> getPeople() {
|
||||
Bundle bundle = ourClient.search().forResource(Person.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute();
|
||||
return BundleUtil.toListOfResourcesOfType(ourCtx, bundle, Person.class);
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testWebsocketSubscription() throws Exception {
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
|
||||
subscription.setCriteria("Observation?status=final");
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
IIdType mySubscriptionId = methodOutcome.getId();
|
||||
|
||||
// Wait for the subscription to be activated
|
||||
await().until(() -> activeSubscriptionCount() == 3);
|
||||
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
WebSocketClient myWebSocketClient = new WebSocketClient();
|
||||
SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + port + "/websocket");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
|
||||
/*
|
||||
* Create a matching resource
|
||||
*/
|
||||
Observation obs = new Observation();
|
||||
obs.setStatus(Observation.ObservationStatus.FINAL);
|
||||
ourClient.create().resource(obs).execute();
|
||||
|
||||
// Give some time for the subscription to deliver
|
||||
Thread.sleep(2000);
|
||||
|
||||
/*
|
||||
* Ensure that we receive a ping on the websocket
|
||||
*/
|
||||
waitForSize(1, () -> mySocketImplementation.myPingCount);
|
||||
|
||||
/*
|
||||
* Clean up
|
||||
*/
|
||||
ourClient.delete().resourceById(mySubscriptionId).execute();
|
||||
}
|
||||
|
||||
private int activeSubscriptionCount() {
|
||||
return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size();
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
"spring.batch.job.enabled=false",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr5",
|
||||
"hapi.fhir.fhir_version=r5",
|
||||
"hapi.fhir.subscription.websocket_enabled=true"
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
"hapi.fhir.subscription.websocket_enabled=true"
|
||||
})
|
||||
public class ExampleServerR5IT {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user