Feature/elastic on boot (#856)
* Fixing up elastic for Spring Boot * Adding class shadowing for issue reported on https://github.com/hapifhir/hapi-fhir/pull/7242 * Formatting * corrected condition * fix import * fix2 * crappy fix3 * fix actuator endpoint * Simplified expression * Ironed out a few legacy issues * more rework * major overhaul * Disabling invalid test * Reverting back to defaults for text searches * Added default hibernate settings from the EnvironmentHelper * Formatting * Added comment on class shadow * Added missing default
This commit is contained in:
committed by
GitHub
parent
4265137b12
commit
9576cfa9b5
33
pom.xml
33
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<hapi.fhir.jpa.server.starter.revision>2</hapi.fhir.jpa.server.starter.revision>
|
||||
<hapi.fhir.jpa.server.starter.revision>3</hapi.fhir.jpa.server.starter.revision>
|
||||
<clinical-reasoning.version>3.26.0</clinical-reasoning.version>
|
||||
</properties>
|
||||
|
||||
@@ -60,6 +60,16 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
|
||||
<version>${spring_boot_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>co.elastic.clients</groupId>
|
||||
<artifactId>elasticsearch-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
@@ -265,12 +275,7 @@
|
||||
<artifactId>moment</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- The following dependencies are only needed for automated unit tests, you do not neccesarily need them to run the example. -->
|
||||
<dependency>
|
||||
<groupId>co.elastic.clients</groupId>
|
||||
<artifactId>elasticsearch-java</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
@@ -386,17 +391,15 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<version>1.1.0-M1</version>
|
||||
</dependency>
|
||||
<!--
|
||||
This will be included as well as using Spring Automatic Configuration
|
||||
once spring-ai and io.modelcontextprotocol.sdk are on par
|
||||
-->
|
||||
<!--<dependency>
|
||||
|
||||
<!--implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc:1.1.0-M1")-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-server</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>-->
|
||||
<version>1.1.0-M1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.modelcontextprotocol.sdk</groupId>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2025 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.provider;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.annotation.RawParam;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.DateAndListParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
// Can be removed when https://github.com/hapifhir/hapi-fhir/issues/7255 is resolved
|
||||
public abstract class BaseJpaResourceProviderObservation<T extends IBaseResource> extends BaseJpaResourceProvider<T> {
|
||||
|
||||
/**
|
||||
* Observation/$lastn
|
||||
*/
|
||||
@Operation(name = JpaConstants.OPERATION_LASTN, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET)
|
||||
public IBundleProvider observationLastN(
|
||||
jakarta.servlet.http.HttpServletRequest theServletRequest,
|
||||
jakarta.servlet.http.HttpServletResponse theServletResponse,
|
||||
ca.uhn.fhir.rest.api.server.RequestDetails theRequestDetails,
|
||||
@Description(
|
||||
formalDefinition =
|
||||
"Results from this method are returned across multiple pages. This parameter controls the size of those pages.")
|
||||
@OperationParam(name = Constants.PARAM_COUNT, typeName = "unsignedInt")
|
||||
IPrimitiveType<Integer> theCount,
|
||||
@Description(shortDefinition = "The classification of the type of observation")
|
||||
@OperationParam(name = "category")
|
||||
TokenAndListParam theCategory,
|
||||
@Description(shortDefinition = "The code of the observation type") @OperationParam(name = "code")
|
||||
TokenAndListParam theCode,
|
||||
@Description(shortDefinition = "The effective date of the observation") @OperationParam(name = "date")
|
||||
DateAndListParam theDate,
|
||||
@Description(shortDefinition = "The subject that the observation is about (if patient)")
|
||||
@OperationParam(name = "patient")
|
||||
ReferenceAndListParam thePatient,
|
||||
@Description(shortDefinition = "The subject that the observation is about")
|
||||
@OperationParam(name = "subject")
|
||||
ReferenceAndListParam theSubject,
|
||||
@Description(shortDefinition = "The maximum number of observations to return for each observation code")
|
||||
@OperationParam(name = "max", typeName = "integer", min = 0, max = 1)
|
||||
IPrimitiveType<Integer> theMax,
|
||||
@RawParam Map<String, List<String>> theAdditionalRawParams) {
|
||||
startRequest(theServletRequest);
|
||||
try {
|
||||
SearchParameterMap paramMap = new SearchParameterMap();
|
||||
paramMap.add(org.hl7.fhir.r4.model.Observation.SP_CATEGORY, theCategory);
|
||||
paramMap.add(org.hl7.fhir.r4.model.Observation.SP_CODE, theCode);
|
||||
paramMap.add(org.hl7.fhir.r4.model.Observation.SP_DATE, theDate);
|
||||
if (thePatient != null) {
|
||||
paramMap.add(org.hl7.fhir.r4.model.Observation.SP_PATIENT, thePatient);
|
||||
}
|
||||
if (theSubject != null) {
|
||||
paramMap.add(org.hl7.fhir.r4.model.Observation.SP_SUBJECT, theSubject);
|
||||
}
|
||||
if (theMax != null) {
|
||||
paramMap.setLastNMax(theMax.getValue());
|
||||
|
||||
/**
|
||||
* The removal of the original raw parameter is required as every implementing class
|
||||
* has the "Observation" resource class defined. For this resource, the max parameter
|
||||
* is not supported and thus has to be removed before the use of "translateRawParameters".
|
||||
*/
|
||||
if (theAdditionalRawParams != null) theAdditionalRawParams.remove("max");
|
||||
}
|
||||
if (theCount != null) {
|
||||
paramMap.setCount(theCount.getValue());
|
||||
}
|
||||
|
||||
getDao().translateRawParameters(theAdditionalRawParams, paramMap);
|
||||
|
||||
return ((IFhirResourceDaoObservation<?>) getDao())
|
||||
.observationsLastN(paramMap, theRequestDetails, theServletResponse);
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
@@ -26,7 +25,7 @@ import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@ServletComponentScan(basePackageClasses = {RestfulServer.class})
|
||||
@SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class, ThymeleafAutoConfiguration.class})
|
||||
@SpringBootApplication(exclude = {ThymeleafAutoConfiguration.class})
|
||||
@Import({
|
||||
StarterCrR4Config.class,
|
||||
StarterCrDstu3Config.class,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ca.uhn.fhir.jpa.starter.annotations;
|
||||
|
||||
import ca.uhn.fhir.jpa.starter.AppProperties;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
@@ -10,9 +10,7 @@ public class OnImplementationGuidesPresent implements Condition {
|
||||
@Override
|
||||
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
|
||||
|
||||
AppProperties config = Binder.get(conditionContext.getEnvironment())
|
||||
.bind("hapi.fhir", AppProperties.class)
|
||||
.orElse(null);
|
||||
AppProperties config = EnvironmentHelper.getConfiguration(conditionContext, "hapi.fhir", AppProperties.class);
|
||||
if (config == null) return false;
|
||||
if (config.getImplementationGuides() == null) return false;
|
||||
return !config.getImplementationGuides().isEmpty();
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package ca.uhn.fhir.jpa.starter.common;
|
||||
|
||||
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/** Shared configuration for Elasticsearch */
|
||||
@Configuration
|
||||
public class ElasticsearchConfig {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElasticsearchConfig.class);
|
||||
|
||||
@Bean
|
||||
public ElasticsearchSvcImpl elasticsearchSvc(ConfigurableEnvironment configurableEnvironment) {
|
||||
if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) {
|
||||
String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment);
|
||||
if (elasticsearchUrl.startsWith("http")) {
|
||||
elasticsearchUrl = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://") + 3);
|
||||
}
|
||||
String elasticsearchProtocol = EnvironmentHelper.getElasticsearchServerProtocol(configurableEnvironment);
|
||||
String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment);
|
||||
String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment);
|
||||
ourLog.info("Configuring elasticsearch {} {}", elasticsearchProtocol, elasticsearchUrl);
|
||||
return new ElasticsearchSvcImpl(
|
||||
elasticsearchProtocol, elasticsearchUrl, elasticsearchUsername, elasticsearchPassword);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMod
|
||||
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
|
||||
import ca.uhn.fhir.jpa.starter.AppProperties;
|
||||
import ca.uhn.fhir.jpa.starter.elastic.ElasticsearchBootSvcImpl;
|
||||
import ca.uhn.fhir.jpa.starter.util.JpaHibernatePropertiesProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.match.deliver.email.EmailSenderImpl;
|
||||
import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender;
|
||||
@@ -19,10 +20,7 @@ import org.hl7.fhir.r4.model.Bundle.BundleType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@@ -36,6 +34,7 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@Import(ElasticsearchBootSvcImpl.class)
|
||||
public class FhirServerConfigCommon {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirServerConfigCommon.class);
|
||||
@@ -274,7 +273,15 @@ public class FhirServerConfigCommon {
|
||||
ourLog.debug("Server configured to Store Meta Source: {}", appProperties.getStore_meta_source_information());
|
||||
jpaStorageSettings.setStoreMetaSourceInformation(appProperties.getStore_meta_source_information());
|
||||
|
||||
storageSettings(appProperties, jpaStorageSettings);
|
||||
jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches());
|
||||
jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
|
||||
jpaStorageSettings.setDefaultSearchParamsCanBeOverridden(
|
||||
appProperties.getAllow_override_default_search_params());
|
||||
|
||||
jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level());
|
||||
|
||||
jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource());
|
||||
jpaStorageSettings.setIndexIdentifierOfType(appProperties.getEnable_index_of_type());
|
||||
return jpaStorageSettings;
|
||||
}
|
||||
|
||||
@@ -332,19 +339,6 @@ public class FhirServerConfigCommon {
|
||||
return new JpaHibernatePropertiesProvider(myEntityManagerFactory);
|
||||
}
|
||||
|
||||
protected StorageSettings storageSettings(AppProperties appProperties, JpaStorageSettings jpaStorageSettings) {
|
||||
jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches());
|
||||
jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
|
||||
jpaStorageSettings.setDefaultSearchParamsCanBeOverridden(
|
||||
appProperties.getAllow_override_default_search_params());
|
||||
|
||||
jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level());
|
||||
|
||||
jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource());
|
||||
jpaStorageSettings.setIndexIdentifierOfType(appProperties.getEnable_index_of_type());
|
||||
return jpaStorageSettings;
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Bean
|
||||
public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) {
|
||||
|
||||
@@ -9,5 +9,5 @@ import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Conditional(OnDSTU3Condition.class)
|
||||
@Import({JpaDstu3Config.class, StarterJpaConfig.class, StarterCrDstu3Config.class, ElasticsearchConfig.class})
|
||||
@Import({JpaDstu3Config.class, StarterJpaConfig.class, StarterCrDstu3Config.class})
|
||||
public class FhirServerConfigDstu3 {}
|
||||
|
||||
@@ -10,11 +10,5 @@ import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Conditional(OnR4Condition.class)
|
||||
@Import({
|
||||
JpaR4Config.class,
|
||||
StarterJpaConfig.class,
|
||||
StarterCrR4Config.class,
|
||||
ElasticsearchConfig.class,
|
||||
StarterIpsConfig.class
|
||||
})
|
||||
@Import({JpaR4Config.class, StarterJpaConfig.class, StarterCrR4Config.class, StarterIpsConfig.class})
|
||||
public class FhirServerConfigR4 {}
|
||||
|
||||
@@ -9,5 +9,5 @@ import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Conditional(OnR4BCondition.class)
|
||||
@Import({JpaR4BConfig.class, SubscriptionTopicConfig.class, StarterJpaConfig.class, ElasticsearchConfig.class})
|
||||
@Import({JpaR4BConfig.class, SubscriptionTopicConfig.class, StarterJpaConfig.class})
|
||||
public class FhirServerConfigR4B {}
|
||||
|
||||
@@ -9,5 +9,5 @@ import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Conditional(OnR5Condition.class)
|
||||
@Import({StarterJpaConfig.class, JpaR5Config.class, SubscriptionTopicConfig.class, ElasticsearchConfig.class})
|
||||
@Import({StarterJpaConfig.class, JpaR5Config.class, SubscriptionTopicConfig.class})
|
||||
public class FhirServerConfigR5 {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ca.uhn.fhir.jpa.starter.common;
|
||||
|
||||
import ca.uhn.fhir.jpa.starter.AppProperties;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
@@ -9,9 +9,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
public class OnPartitionModeEnabled implements Condition {
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
var appProperties = Binder.get(context.getEnvironment())
|
||||
.bind("hapi.fhir", AppProperties.class)
|
||||
.orElse(null);
|
||||
var appProperties = EnvironmentHelper.getConfiguration(context, "hapi.fhir", AppProperties.class);
|
||||
if (appProperties == null) return false;
|
||||
return appProperties.getPartitioning() != null;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ import ca.uhn.fhir.jpa.starter.annotations.OnImplementationGuidesPresent;
|
||||
import ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory;
|
||||
import ca.uhn.fhir.jpa.starter.ig.ExtendedPackageInstallationSpec;
|
||||
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.mdm.provider.MdmProviderLoader;
|
||||
@@ -79,13 +78,17 @@ import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import com.google.common.base.Strings;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
@@ -93,13 +96,11 @@ 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.io.IOException;
|
||||
import java.util.*;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -123,9 +124,6 @@ public class StarterJpaConfig {
|
||||
return new StaleSearchDeletingSvcImpl();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ConfigurableEnvironment configurableEnvironment;
|
||||
|
||||
/**
|
||||
* Customize the default/max page sizes for search results. You can set these however
|
||||
* you want, although very large page sizes will require a lot of RAM.
|
||||
@@ -151,22 +149,46 @@ public class StarterJpaConfig {
|
||||
@Primary
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
|
||||
JpaProperties theJpaProperties,
|
||||
DataSource myDataSource,
|
||||
ConfigurableListableBeanFactory myConfigurableListableBeanFactory,
|
||||
FhirContext theFhirContext,
|
||||
JpaStorageSettings theStorageSettings) {
|
||||
LocalContainerEntityManagerFactoryBean retVal = HapiEntityManagerFactoryUtil.newEntityManagerFactory(
|
||||
myConfigurableListableBeanFactory, theFhirContext, theStorageSettings);
|
||||
retVal.setPersistenceUnitName("HAPI_PU");
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean =
|
||||
HapiEntityManagerFactoryUtil.newEntityManagerFactory(
|
||||
myConfigurableListableBeanFactory, theFhirContext, theStorageSettings);
|
||||
|
||||
try {
|
||||
retVal.setDataSource(myDataSource);
|
||||
} catch (Exception e) {
|
||||
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
|
||||
}
|
||||
retVal.setJpaProperties(
|
||||
EnvironmentHelper.getHibernateProperties(configurableEnvironment, myConfigurableListableBeanFactory));
|
||||
return retVal;
|
||||
// Spring Boot Autoconfiguration defaults
|
||||
theJpaProperties
|
||||
.getProperties()
|
||||
.putIfAbsent(AvailableSettings.SCANNER, "org.hibernate.boot.archive.scan.internal.DisabledScanner");
|
||||
theJpaProperties
|
||||
.getProperties()
|
||||
.putIfAbsent(AvailableSettings.IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.class.getName());
|
||||
theJpaProperties
|
||||
.getProperties()
|
||||
.putIfAbsent(
|
||||
AvailableSettings.PHYSICAL_NAMING_STRATEGY,
|
||||
CamelCaseToUnderscoresNamingStrategy.class.getName());
|
||||
|
||||
// Hibernate Search defaults
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.FORMAT_SQL, "false");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.SHOW_SQL, "false");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.HBM2DDL_AUTO, "update");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.STATEMENT_BATCH_SIZE, "20");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.USE_QUERY_CACHE, "false");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.USE_SECOND_LEVEL_CACHE, "false");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.USE_STRUCTURED_CACHE, "false");
|
||||
theJpaProperties.getProperties().putIfAbsent(AvailableSettings.USE_MINIMAL_PUTS, "false");
|
||||
|
||||
// Hibernate Search defaults
|
||||
theJpaProperties.getProperties().putIfAbsent(HibernateOrmMapperSettings.ENABLED, "false");
|
||||
|
||||
entityManagerFactoryBean.setPersistenceUnitName("HAPI_PU");
|
||||
entityManagerFactoryBean.setJpaPropertyMap(theJpaProperties.getProperties());
|
||||
entityManagerFactoryBean.setDataSource(myDataSource);
|
||||
|
||||
return entityManagerFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -213,8 +235,7 @@ public class StarterJpaConfig {
|
||||
Batch2JobRegisterer batch2JobRegisterer,
|
||||
FhirContext fhirContext,
|
||||
TransactionProcessor transactionProcessor,
|
||||
IHapiPackageCacheManager iHapiPackageCacheManager)
|
||||
throws IOException {
|
||||
IHapiPackageCacheManager iHapiPackageCacheManager) {
|
||||
|
||||
batch2JobRegisterer.start();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ca.uhn.fhir.jpa.starter.common.validation;
|
||||
|
||||
import ca.uhn.fhir.jpa.starter.AppProperties;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
@@ -10,9 +10,8 @@ public class OnRemoteTerminologyPresent implements Condition {
|
||||
@Override
|
||||
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
|
||||
|
||||
AppProperties config = Binder.get(conditionContext.getEnvironment())
|
||||
.bind("hapi.fhir", AppProperties.class)
|
||||
.orElse(null);
|
||||
AppProperties config = EnvironmentHelper.getConfiguration(conditionContext, "hapi.fhir", AppProperties.class);
|
||||
|
||||
if (config == null) return false;
|
||||
if (config.getRemoteTerminologyServicesMap() == null) return false;
|
||||
return !config.getRemoteTerminologyServicesMap().isEmpty();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package ca.uhn.fhir.jpa.starter.elastic;
|
||||
|
||||
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchProperties;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
public class ElasticConfigCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
|
||||
return EnvironmentHelper.getConfiguration(
|
||||
theConditionContext, "spring.elasticsearch", ElasticsearchProperties.class)
|
||||
!= null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package ca.uhn.fhir.jpa.starter.elastic;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.dao.TolerantJsonParser;
|
||||
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.search.lastn.IElasticsearchSvc;
|
||||
import ca.uhn.fhir.jpa.search.lastn.json.ObservationJson;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch._types.FieldValue;
|
||||
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Conditional(ElasticConfigCondition.class)
|
||||
public class ElasticsearchBootSvcImpl implements IElasticsearchSvc {
|
||||
|
||||
// Index Constants
|
||||
public static final String OBSERVATION_INDEX = "observation_index";
|
||||
public static final String OBSERVATION_CODE_INDEX = "code_index";
|
||||
public static final String OBSERVATION_INDEX_SCHEMA_FILE = "ObservationIndexSchema.json";
|
||||
public static final String OBSERVATION_CODE_INDEX_SCHEMA_FILE = "ObservationCodeIndexSchema.json";
|
||||
|
||||
// Aggregation Constants
|
||||
|
||||
// Observation index document element names
|
||||
private static final String OBSERVATION_IDENTIFIER_FIELD_NAME = "identifier";
|
||||
|
||||
// Code index document element names
|
||||
private static final String CODE_HASH = "codingcode_system_hash";
|
||||
private static final String CODE_TEXT = "text";
|
||||
|
||||
private static final String OBSERVATION_RESOURCE_NAME = "Observation";
|
||||
|
||||
private final ElasticsearchClient myRestHighLevelClient;
|
||||
|
||||
private final FhirContext myContext;
|
||||
|
||||
public ElasticsearchBootSvcImpl(ElasticsearchClient client, FhirContext fhirContext) {
|
||||
|
||||
myContext = fhirContext;
|
||||
myRestHighLevelClient = client;
|
||||
|
||||
try {
|
||||
createObservationIndexIfMissing();
|
||||
createObservationCodeIndexIfMissing();
|
||||
} catch (IOException theE) {
|
||||
throw new RuntimeException(Msg.code(1175) + "Failed to create document index", theE);
|
||||
}
|
||||
}
|
||||
|
||||
private String getIndexSchema(String theSchemaFileName) throws IOException {
|
||||
InputStreamReader input =
|
||||
new InputStreamReader(ElasticsearchSvcImpl.class.getResourceAsStream(theSchemaFileName));
|
||||
BufferedReader reader = new BufferedReader(input);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String str;
|
||||
while ((str = reader.readLine()) != null) {
|
||||
sb.append(str);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void createObservationIndexIfMissing() throws IOException {
|
||||
if (indexExists(OBSERVATION_INDEX)) {
|
||||
return;
|
||||
}
|
||||
String observationMapping = getIndexSchema(OBSERVATION_INDEX_SCHEMA_FILE);
|
||||
if (!createIndex(OBSERVATION_INDEX, observationMapping)) {
|
||||
throw new RuntimeException(Msg.code(1176) + "Failed to create observation index");
|
||||
}
|
||||
}
|
||||
|
||||
private void createObservationCodeIndexIfMissing() throws IOException {
|
||||
if (indexExists(OBSERVATION_CODE_INDEX)) {
|
||||
return;
|
||||
}
|
||||
String observationCodeMapping = getIndexSchema(OBSERVATION_CODE_INDEX_SCHEMA_FILE);
|
||||
if (!createIndex(OBSERVATION_CODE_INDEX, observationCodeMapping)) {
|
||||
throw new RuntimeException(Msg.code(1177) + "Failed to create observation code index");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean createIndex(String theIndexName, String theMapping) throws IOException {
|
||||
return myRestHighLevelClient
|
||||
.indices()
|
||||
.create(cir -> cir.index(theIndexName).withJson(new StringReader(theMapping)))
|
||||
.acknowledged();
|
||||
}
|
||||
|
||||
private boolean indexExists(String theIndexName) throws IOException {
|
||||
ExistsRequest request = new ExistsRequest.Builder().index(theIndexName).build();
|
||||
return myRestHighLevelClient.indices().exists(request).value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IBaseResource> getObservationResources(Collection<? extends IResourcePersistentId> thePids) {
|
||||
SearchRequest searchRequest = buildObservationResourceSearchRequest(thePids);
|
||||
try {
|
||||
SearchResponse<ObservationJson> observationDocumentResponse =
|
||||
myRestHighLevelClient.search(searchRequest, ObservationJson.class);
|
||||
List<Hit<ObservationJson>> observationDocumentHits =
|
||||
observationDocumentResponse.hits().hits();
|
||||
IParser parser = TolerantJsonParser.createWithLenientErrorHandling(myContext, null);
|
||||
Class<? extends IBaseResource> resourceType =
|
||||
myContext.getResourceDefinition(OBSERVATION_RESOURCE_NAME).getImplementingClass();
|
||||
/**
|
||||
* @see ca.uhn.fhir.jpa.dao.BaseHapiFhirDao#toResource(Class, IBaseResourceEntity, Collection, boolean) for
|
||||
* details about parsing raw json to BaseResource
|
||||
*/
|
||||
return observationDocumentHits.stream()
|
||||
.map(Hit::source)
|
||||
.map(observationJson -> parser.parseResource(resourceType, observationJson.getResource()))
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException theE) {
|
||||
throw new InvalidRequestException(
|
||||
Msg.code(2003) + "Unable to execute observation document query for provided IDs " + thePids, theE);
|
||||
}
|
||||
}
|
||||
|
||||
private SearchRequest buildObservationResourceSearchRequest(Collection<? extends IResourcePersistentId> thePids) {
|
||||
List<FieldValue> values = thePids.stream()
|
||||
.map(Object::toString)
|
||||
.map(v -> FieldValue.of(v))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return SearchRequest.of(sr -> sr.index(OBSERVATION_INDEX)
|
||||
.query(qb -> qb.bool(bb -> bb.must(bbm -> {
|
||||
bbm.terms(terms ->
|
||||
terms.field(OBSERVATION_IDENTIFIER_FIELD_NAME).terms(termsb -> termsb.value(values)));
|
||||
return bbm;
|
||||
})))
|
||||
.size(thePids.size()));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void refreshIndex(String theIndexName) throws IOException {
|
||||
myRestHighLevelClient.indices().refresh(fn -> fn.index(theIndexName));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package ca.uhn.fhir.jpa.starter.ig;
|
||||
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
public class IgConfigCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
|
||||
String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ig_runtime_upload_enabled");
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,18 @@ import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import org.hl7.fhir.r4.model.Base64BinaryType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Conditional({OnR4Condition.class, IgConfigCondition.class})
|
||||
@Conditional({OnR4Condition.class})
|
||||
@ConditionalOnProperty(name = "hapi.fhir.ig_runtime_upload_enabled", havingValue = "true")
|
||||
@Service
|
||||
public class ImplementationGuideR4OperationProvider implements IImplementationGuideOperationProvider {
|
||||
|
||||
IPackageInstallerSvc packageInstallerSvc;
|
||||
final IPackageInstallerSvc packageInstallerSvc;
|
||||
|
||||
public ImplementationGuideR4OperationProvider(IPackageInstallerSvc packageInstallerSvc) {
|
||||
this.packageInstallerSvc = packageInstallerSvc;
|
||||
|
||||
@@ -7,16 +7,18 @@ import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import org.hl7.fhir.r5.model.Base64BinaryType;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Conditional({OnR5Condition.class, IgConfigCondition.class})
|
||||
@Conditional({OnR5Condition.class})
|
||||
@ConditionalOnProperty(name = "hapi.fhir.ig_runtime_upload_enabled", havingValue = "true")
|
||||
@Service
|
||||
public class ImplementationGuideR5OperationProvider implements IImplementationGuideOperationProvider {
|
||||
|
||||
IPackageInstallerSvc packageInstallerSvc;
|
||||
final IPackageInstallerSvc packageInstallerSvc;
|
||||
|
||||
public ImplementationGuideR5OperationProvider(IPackageInstallerSvc packageInstallerSvc) {
|
||||
this.packageInstallerSvc = packageInstallerSvc;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package ca.uhn.fhir.jpa.starter.ips;
|
||||
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
public class IpsConfigCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
|
||||
String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ips_enabled");
|
||||
return Boolean.parseBoolean(property);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
|
||||
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
|
||||
import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
|
||||
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Conditional(IpsConfigCondition.class)
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "hapi.fhir.ips_enabled", havingValue = "true")
|
||||
public class StarterIpsConfig {
|
||||
@Bean
|
||||
IIpsGenerationStrategy ipsGenerationStrategy() {
|
||||
|
||||
@@ -8,14 +8,14 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
|
||||
import ca.uhn.hapi.fhir.cdshooks.module.CdsHooksObjectMapperFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.server.McpServer;
|
||||
import io.modelcontextprotocol.server.McpSyncServer;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
|
||||
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
|
||||
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -30,19 +30,17 @@ import java.util.List;
|
||||
prefix = "spring.ai.mcp.server",
|
||||
name = {"enabled"},
|
||||
havingValue = "true")
|
||||
@Import(McpServerStreamableHttpProperties.class)
|
||||
public class McpServerConfig {
|
||||
|
||||
private static final String SSE_ENDPOINT = "/sse";
|
||||
private static final String SSE_MESSAGE_ENDPOINT = "/mcp/message";
|
||||
|
||||
@Bean
|
||||
public McpSyncServer syncServer(
|
||||
List<McpBridge> mcpBridges, McpStreamableServerTransportProvider transportProvider) {
|
||||
return McpServer.sync(transportProvider)
|
||||
.tools(mcpBridges.stream()
|
||||
.flatMap(bridge -> bridge.generateTools().stream())
|
||||
.toList())
|
||||
.build();
|
||||
public List<McpServerFeatures.SyncToolSpecification> syncServer(List<McpBridge> mcpBridges) {
|
||||
return mcpBridges.stream()
|
||||
.flatMap(bridge -> bridge.generateTools().stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -63,11 +61,11 @@ public class McpServerConfig {
|
||||
|
||||
@Bean
|
||||
public HttpServletStreamableServerTransportProvider servletSseServerTransportProvider(
|
||||
/*McpServerProperties properties*/ ) {
|
||||
McpServerStreamableHttpProperties properties) {
|
||||
|
||||
return HttpServletStreamableServerTransportProvider.builder()
|
||||
.disallowDelete(false)
|
||||
.mcpEndpoint(SSE_MESSAGE_ENDPOINT)
|
||||
.mcpEndpoint(properties.getMcpEndpoint())
|
||||
.objectMapper(new ObjectMapper())
|
||||
// .contextExtractor((serverRequest, context) -> context)
|
||||
.build();
|
||||
@@ -75,7 +73,8 @@ public class McpServerConfig {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean customServletBean(
|
||||
HttpServletStreamableServerTransportProvider transportProvider /*, McpServerProperties properties*/) {
|
||||
return new ServletRegistrationBean<>(transportProvider, SSE_MESSAGE_ENDPOINT, SSE_ENDPOINT);
|
||||
HttpServletStreamableServerTransportProvider transportProvider,
|
||||
McpServerStreamableHttpProperties properties) {
|
||||
return new ServletRegistrationBean<>(transportProvider, properties.getMcpEndpoint(), SSE_ENDPOINT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
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;
|
||||
@@ -20,7 +20,7 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Configuration
|
||||
@Conditional(MdmConfigCondition.class)
|
||||
@ConditionalOnProperty(prefix = "hapi.fhir", name = "mdm_enabled")
|
||||
@Import({MdmConsumerConfig.class, MdmSubmitterConfig.class, NicknameServiceConfig.class})
|
||||
public class MdmConfig {
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package ca.uhn.fhir.jpa.starter.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);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,7 @@
|
||||
package ca.uhn.fhir.jpa.starter.util;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
|
||||
import ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers;
|
||||
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
|
||||
import org.apache.lucene.util.Version;
|
||||
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings;
|
||||
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.backend.lucene.lowlevel.directory.impl.LocalFileSystemDirectoryProvider;
|
||||
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.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
@@ -25,142 +10,11 @@ import org.springframework.core.env.PropertySource;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
public class EnvironmentHelper {
|
||||
|
||||
public static Properties getHibernateProperties(
|
||||
ConfigurableEnvironment environment, ConfigurableListableBeanFactory myConfigurableListableBeanFactory) {
|
||||
Properties properties = new Properties();
|
||||
Map<String, Object> jpaProps = getPropertiesStartingWith(environment, "spring.jpa.properties");
|
||||
for (Map.Entry<String, Object> entry : jpaProps.entrySet()) {
|
||||
String strippedKey = entry.getKey().replace("spring.jpa.properties.", "");
|
||||
properties.put(strippedKey, entry.getValue().toString());
|
||||
}
|
||||
|
||||
// also check for JPA properties set as environment variables, this is slightly hacky and doesn't cover all
|
||||
// the naming conventions Springboot allows
|
||||
// but there doesn't seem to be a better/deterministic way to get these properties when they are set as ENV
|
||||
// variables and this at least provides
|
||||
// a way to set them (in a docker container, for instance)
|
||||
Map<String, Object> jpaPropsEnv = getPropertiesStartingWith(environment, "SPRING_JPA_PROPERTIES");
|
||||
for (Map.Entry<String, Object> entry : jpaPropsEnv.entrySet()) {
|
||||
String strippedKey = entry.getKey().replace("SPRING_JPA_PROPERTIES_", "");
|
||||
strippedKey = strippedKey.replaceAll("_", ".");
|
||||
strippedKey = strippedKey.toLowerCase();
|
||||
properties.put(strippedKey, entry.getValue().toString());
|
||||
}
|
||||
|
||||
// Spring Boot Autoconfiguration defaults
|
||||
properties.putIfAbsent(AvailableSettings.SCANNER, "org.hibernate.boot.archive.scan.internal.DisabledScanner");
|
||||
properties.putIfAbsent(
|
||||
AvailableSettings.IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.class.getName());
|
||||
properties.putIfAbsent(
|
||||
AvailableSettings.PHYSICAL_NAMING_STRATEGY, CamelCaseToUnderscoresNamingStrategy.class.getName());
|
||||
// TODO The bean factory should be added as parameter but that requires that it can be injected from the
|
||||
// entityManagerFactory bean from xBaseConfig
|
||||
// properties.putIfAbsent(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
|
||||
|
||||
// hapi-fhir-jpaserver-base "sensible defaults"
|
||||
Map<String, Object> hapiJpaPropertyMap = new HapiFhirLocalContainerEntityManagerFactoryBean(
|
||||
myConfigurableListableBeanFactory)
|
||||
.getJpaPropertyMap();
|
||||
hapiJpaPropertyMap.forEach(properties::putIfAbsent);
|
||||
|
||||
// hapi-fhir-jpaserver-starter defaults
|
||||
properties.putIfAbsent(AvailableSettings.FORMAT_SQL, false);
|
||||
properties.putIfAbsent(AvailableSettings.SHOW_SQL, false);
|
||||
properties.putIfAbsent(AvailableSettings.HBM2DDL_AUTO, "update");
|
||||
properties.putIfAbsent(AvailableSettings.STATEMENT_BATCH_SIZE, 20);
|
||||
properties.putIfAbsent(AvailableSettings.USE_QUERY_CACHE, false);
|
||||
properties.putIfAbsent(AvailableSettings.USE_SECOND_LEVEL_CACHE, false);
|
||||
properties.putIfAbsent(AvailableSettings.USE_STRUCTURED_CACHE, false);
|
||||
properties.putIfAbsent(AvailableSettings.USE_MINIMAL_PUTS, false);
|
||||
|
||||
// Hibernate Search defaults
|
||||
properties.putIfAbsent(HibernateOrmMapperSettings.ENABLED, false);
|
||||
if (Boolean.parseBoolean(String.valueOf(properties.get(HibernateOrmMapperSettings.ENABLED)))) {
|
||||
if (isElasticsearchEnabled(environment)) {
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(BackendSettings.TYPE), ElasticsearchBackendSettings.TYPE_NAME);
|
||||
} else {
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(BackendSettings.TYPE), LuceneBackendSettings.TYPE_NAME);
|
||||
}
|
||||
|
||||
if (properties
|
||||
.get(BackendSettings.backendKey(BackendSettings.TYPE))
|
||||
.equals(LuceneBackendSettings.TYPE_NAME)) {
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE),
|
||||
LocalFileSystemDirectoryProvider.NAME);
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_ROOT), "target/lucenefiles");
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER),
|
||||
HapiHSearchAnalysisConfigurers.HapiLuceneAnalysisConfigurer.class.getName());
|
||||
properties.putIfAbsent(
|
||||
BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), Version.LATEST);
|
||||
|
||||
} else if (properties
|
||||
.get(BackendSettings.backendKey(BackendSettings.TYPE))
|
||||
.equals(ElasticsearchBackendSettings.TYPE_NAME)) {
|
||||
ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder();
|
||||
IndexStatus requiredIndexStatus =
|
||||
environment.getProperty("elasticsearch.required_index_status", IndexStatus.class);
|
||||
builder.setRequiredIndexStatus(requireNonNullElse(requiredIndexStatus, IndexStatus.YELLOW));
|
||||
builder.setHosts(getElasticsearchServerUrl(environment));
|
||||
builder.setUsername(getElasticsearchServerUsername(environment));
|
||||
builder.setPassword(getElasticsearchServerPassword(environment));
|
||||
builder.setProtocol(getElasticsearchServerProtocol(environment));
|
||||
SchemaManagementStrategyName indexSchemaManagementStrategy = environment.getProperty(
|
||||
"elasticsearch.schema_management_strategy", SchemaManagementStrategyName.class);
|
||||
builder.setIndexSchemaManagementStrategy(
|
||||
requireNonNullElse(indexSchemaManagementStrategy, SchemaManagementStrategyName.CREATE));
|
||||
Boolean refreshAfterWrite =
|
||||
environment.getProperty("elasticsearch.debug.refresh_after_write", Boolean.class);
|
||||
if (refreshAfterWrite == null || !refreshAfterWrite) {
|
||||
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.ASYNC);
|
||||
} else {
|
||||
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.READ_SYNC);
|
||||
}
|
||||
builder.setDebugPrettyPrintJsonLog(requireNonNullElse(
|
||||
environment.getProperty("elasticsearch.debug.pretty_print_json_log", Boolean.class), false));
|
||||
builder.apply(properties);
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unsupported Hibernate Search backend: "
|
||||
+ properties.get(BackendSettings.backendKey(BackendSettings.TYPE)));
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static String getElasticsearchServerUrl(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("elasticsearch.rest_url", String.class);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
public static String getElasticsearchServerPassword(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("elasticsearch.password");
|
||||
}
|
||||
|
||||
public static Boolean isElasticsearchEnabled(ConfigurableEnvironment environment) {
|
||||
if (environment.getProperty("elasticsearch.enabled", Boolean.class) != null) {
|
||||
return environment.getProperty("elasticsearch.enabled", Boolean.class);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
public static <T> T getConfiguration(ConditionContext context, String path, Class<T> clazz) {
|
||||
return Binder.get(context.getEnvironment()).bind(path, clazz).orElse(null);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getPropertiesStartingWith(ConfigurableEnvironment aEnv, String aKeyPrefix) {
|
||||
|
||||
@@ -9,12 +9,16 @@ server:
|
||||
#Adds the option to go to e.g. http://localhost:8080/actuator/health for seeing the running configuration
|
||||
#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints
|
||||
management:
|
||||
health:
|
||||
elasticsearch:
|
||||
enabled: false
|
||||
#The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default.
|
||||
endpoints:
|
||||
enabled-by-default: false
|
||||
web:
|
||||
exposure:
|
||||
include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all
|
||||
# expose only health (default) — change to [health,info,prometheus,metrics] if you want them reachable
|
||||
include: health
|
||||
endpoint:
|
||||
info:
|
||||
enabled: true
|
||||
@@ -63,21 +67,13 @@ spring:
|
||||
|
||||
mcp:
|
||||
server:
|
||||
# Will be enabled once spring-ai-starter-mcp-server is added as dependency
|
||||
# name: FHIR MCP Server
|
||||
# version: 1.0.0
|
||||
# type: SYNC
|
||||
# instructions: "This server provides access to a FHIR RESTful API. You can use it to query FHIR resources, perform operations, and retrieve data in a structured format."
|
||||
# sse-message-endpoint: /mcp/message
|
||||
# capabilities:
|
||||
# tool: true
|
||||
# resource: true
|
||||
# prompt: true
|
||||
# completion: true
|
||||
# stdio: false
|
||||
name: FHIR MCP Server
|
||||
version: 1.0.0
|
||||
instructions: "This server provides access to a FHIR RESTful API. You can use it to query FHIR resources, perform operations, and retrieve data in a structured format."
|
||||
enabled: true
|
||||
streamable-http:
|
||||
mcp-endpoint: /mcp/messages
|
||||
|
||||
#endpoint: /mcp
|
||||
|
||||
#schema:
|
||||
# fhir-enabled: true
|
||||
@@ -93,51 +89,74 @@ spring:
|
||||
# {{schema}}
|
||||
#base-url: /api/v1
|
||||
|
||||
|
||||
autoconfigure:
|
||||
# This exclude is only needed for setups not using Elasticsearch where the elasticsearch sniff is not needed.
|
||||
exclude: org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
|
||||
main:
|
||||
allow-bean-definition-overriding: false
|
||||
allow-circular-references: true
|
||||
flyway:
|
||||
enabled: false
|
||||
baselineOnMigrate: true
|
||||
baseline-on-migrate: true
|
||||
fail-on-missing-locations: false
|
||||
datasource:
|
||||
#url: 'jdbc:h2:file:./target/database/h2'
|
||||
url: jdbc:h2:mem:test_mem
|
||||
username: sa
|
||||
password: null
|
||||
driverClassName: org.h2.Driver
|
||||
max-active: 15
|
||||
driver-class-name: org.h2.Driver
|
||||
|
||||
# database connection pool size
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
# elasticsearch:
|
||||
# uris: http://localhost:9200
|
||||
# username: elastic
|
||||
# password: changeme
|
||||
jpa:
|
||||
properties:
|
||||
hibernate.format_sql: false
|
||||
hibernate.show_sql: false
|
||||
hibernate:
|
||||
hbm2ddl:
|
||||
auto: update
|
||||
jdbc:
|
||||
batch_size: 20
|
||||
cache:
|
||||
use_query_cache: false
|
||||
use_second_level_cache: false
|
||||
use_structured_entries: false
|
||||
use_minimal_puts: false
|
||||
format_sql: false
|
||||
show_sql: false
|
||||
#If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
#If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect
|
||||
#dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect
|
||||
dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
search:
|
||||
enabled: true
|
||||
schema_management:
|
||||
strategy: create
|
||||
### lucene parameters
|
||||
backend:
|
||||
type: lucene
|
||||
directory:
|
||||
type: local-filesystem
|
||||
root: target/lucenefiles
|
||||
analysis:
|
||||
configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer
|
||||
|
||||
#Hibernate dialect is automatically detected except Postgres and H2.
|
||||
#If using H2, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
#If using postgres, then supply the value of ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect
|
||||
hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
# hibernate.hbm2ddl.auto: update
|
||||
# hibernate.jdbc.batch_size: 20
|
||||
# hibernate.cache.use_query_cache: false
|
||||
# hibernate.cache.use_second_level_cache: false
|
||||
# hibernate.cache.use_structured_entries: false
|
||||
# hibernate.cache.use_minimal_puts: false
|
||||
### elastic parameters ===> see also elasticsearch section below <===
|
||||
# backend:
|
||||
# type: elasticsearch
|
||||
# discovery: true
|
||||
# analysis:
|
||||
# configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
|
||||
# hosts: localhost:9200
|
||||
# protocol: http
|
||||
# username: elastic
|
||||
# password: changeme
|
||||
# refresh_after_write: true
|
||||
|
||||
### These settings will enable fulltext search with lucene or elastic
|
||||
hibernate.search.enabled: false
|
||||
### lucene parameters
|
||||
# hibernate.search.backend.type: lucene
|
||||
# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer
|
||||
# hibernate.search.backend.directory.type: local-filesystem
|
||||
# hibernate.search.backend.directory.root: target/lucenefiles
|
||||
# hibernate.search.backend.lucene_version: lucene_current
|
||||
### elastic parameters ===> see also elasticsearch section below <===
|
||||
# hibernate.search.backend.type: elasticsearch
|
||||
# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
|
||||
hapi:
|
||||
fhir:
|
||||
### This flag when enabled to true, will avail evaluate measure operations from CR Module.
|
||||
@@ -201,42 +220,42 @@ hapi:
|
||||
fhir_version: R4
|
||||
### Flag is false by default. This flag enables runtime installation of IG's.
|
||||
ig_runtime_upload_enabled: false
|
||||
### This flag when enabled to true, will avail evaluate measure operations from CR Module.
|
||||
### This flag when enabled to true, will avail evaluate measure operations from CR Module.
|
||||
|
||||
### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers
|
||||
### to determine the FHIR server address
|
||||
# use_apache_address_strategy: false
|
||||
### forces the use of the https:// protocol for the returned server address.
|
||||
### alternatively, it may be set using the X-Forwarded-Proto header.
|
||||
# use_apache_address_strategy_https: false
|
||||
### enables the server to overwrite defaults on HTML, css, etc. under the url pattern of eg. /content/custom **
|
||||
### Folder with custom content MUST be named custom. If omitted then default content applies
|
||||
#custom_content_path: ./custom
|
||||
### enables the server host custom content. If e.g. the value ./configs/app is supplied then the content
|
||||
### will be served under /web/app
|
||||
#app_content_path: ./configs/app
|
||||
### enable to set the Server URL
|
||||
# server_address: http://hapi.fhir.org/baseR4
|
||||
# defer_indexing_for_codesystems_of_size: 101
|
||||
### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed.
|
||||
# validate_resource_status_for_package_upload: false
|
||||
# install_transitive_ig_dependencies: true
|
||||
#implementationguides:
|
||||
### example from registry (packages.fhir.org)
|
||||
# swiss:
|
||||
# name: swiss.mednet.fhir
|
||||
# version: 0.8.0
|
||||
# reloadExisting: false
|
||||
# installMode: STORE_AND_INSTALL
|
||||
# example not from registry
|
||||
# ips_1_0_0:
|
||||
# packageUrl: https://costateixeira.github.io/smart-ips-pilgrimage-fulltest/package.tgz
|
||||
# name: smart.who.int.ips-pilgrimage-test
|
||||
# version: 0.1.0
|
||||
### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers
|
||||
### to determine the FHIR server address
|
||||
# use_apache_address_strategy: false
|
||||
### forces the use of the https:// protocol for the returned server address.
|
||||
### alternatively, it may be set using the X-Forwarded-Proto header.
|
||||
# use_apache_address_strategy_https: false
|
||||
### enables the server to overwrite defaults on HTML, css, etc. under the url pattern of eg. /content/custom **
|
||||
### Folder with custom content MUST be named custom. If omitted then default content applies
|
||||
#custom_content_path: ./custom
|
||||
### enables the server host custom content. If e.g. the value ./configs/app is supplied then the content
|
||||
### will be served under /web/app
|
||||
#app_content_path: ./configs/app
|
||||
### enable to set the Server URL
|
||||
# server_address: http://hapi.fhir.org/baseR4
|
||||
# defer_indexing_for_codesystems_of_size: 101
|
||||
### Flag is true by default. This flag filters resources during package installation, allowing only those resources with a valid status (e.g. active) to be installed.
|
||||
# validate_resource_status_for_package_upload: false
|
||||
# install_transitive_ig_dependencies: true
|
||||
#implementationguides:
|
||||
### example from registry (packages.fhir.org)
|
||||
# swiss:
|
||||
# name: swiss.mednet.fhir
|
||||
# version: 0.8.0
|
||||
# reloadExisting: false
|
||||
# installMode: STORE_AND_INSTALL
|
||||
# example not from registry
|
||||
# ips_1_0_0:
|
||||
# packageUrl: https://costateixeira.github.io/smart-ips-pilgrimage-fulltest/package.tgz
|
||||
# name: smart.who.int.ips-pilgrimage-test
|
||||
# version: 0.1.0
|
||||
# installMode: STORE_AND_INSTALL
|
||||
# additionalResourceFolders:
|
||||
# - example
|
||||
# - example2
|
||||
# - example2
|
||||
# supported_resource_types:
|
||||
# - Patient
|
||||
# - Observation
|
||||
@@ -309,21 +328,21 @@ hapi:
|
||||
- http://loinc.org/*
|
||||
- https://loinc.org/*
|
||||
|
||||
### 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
|
||||
# default_partition_id: 0
|
||||
### 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
|
||||
# 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: true
|
||||
# database_partition_mode_enabled: true
|
||||
### 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: true
|
||||
# patient_id_partitioning_mode: true
|
||||
### 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
|
||||
@@ -432,14 +451,3 @@ hapi:
|
||||
### 1: NORMALIZED_QUANTITY_STORAGE_SUPPORTED
|
||||
### 2: NORMALIZED_QUANTITY_SEARCH_SUPPORTED
|
||||
# normalized_quantity_search_level: 2
|
||||
#elasticsearch:
|
||||
# debug:
|
||||
# pretty_print_json_log: false
|
||||
# refresh_after_write: false
|
||||
# enabled: false
|
||||
# password: SomePassword
|
||||
# required_index_status: YELLOW
|
||||
# rest_url: 'localhost:9200'
|
||||
# protocol: 'http'
|
||||
# schema_management_strategy: CREATE
|
||||
# username: SomeUsername
|
||||
|
||||
@@ -46,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
}, properties = {
|
||||
"spring.profiles.include=storageSettingsTest",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.cr.enabled=true",
|
||||
|
||||
@@ -14,14 +14,14 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = {
|
||||
"hapi.fhir.custom-bean-packages=some.custom.pkg1",
|
||||
"hapi.fhir.custom-interceptor-classes=some.custom.pkg1.CustomInterceptorBean,some.custom.pkg1.CustomInterceptorPojo",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
// "hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4"
|
||||
"hapi.fhir.custom-bean-packages=some.custom.pkg1",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.custom-interceptor-classes=some.custom.pkg1.CustomInterceptorBean,some.custom.pkg1.CustomInterceptorPojo",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
// "hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4"
|
||||
})
|
||||
|
||||
class CustomInterceptorTest {
|
||||
|
||||
@LocalServerPort
|
||||
|
||||
@@ -3,8 +3,8 @@ package ca.uhn.fhir.jpa.starter;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchRestClientFactory;
|
||||
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
|
||||
import ca.uhn.fhir.jpa.starter.elastic.ElasticsearchBootSvcImpl;
|
||||
import ca.uhn.fhir.jpa.test.config.TestElasticsearchContainerHelper;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||
@@ -14,8 +14,6 @@ import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||
import co.elastic.clients.elasticsearch.indices.IndexSettings;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
@@ -27,6 +25,8 @@ import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
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;
|
||||
@@ -42,6 +42,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@Testcontainers
|
||||
@Disabled
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties =
|
||||
{
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
@@ -50,19 +51,19 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
"hapi.fhir.store_resource_in_lucene_index_enabled=true",
|
||||
"hapi.fhir.advanced_lucene_indexing=true",
|
||||
"hapi.fhir.search_index_full_text_enabled=true",
|
||||
|
||||
"elasticsearch.enabled=true",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
// 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.debug.refresh_after_write=true",
|
||||
"elasticsearch.protocol=http",
|
||||
|
||||
"spring.elasticsearch.uris=http://localhost:9200",
|
||||
"spring.elasticsearch.username=elastic",
|
||||
"spring.elasticsearch.password=changeme",
|
||||
"spring.main.allow-bean-definition-overriding=true",
|
||||
"spring.jpa.properties.hibernate.search.enabled=true",
|
||||
"spring.jpa.properties.hibernate.search.backend.type=elasticsearch",
|
||||
"spring.jpa.properties.hibernate.search.backend.analysis.configurer=ca.uhn.fhir.jpa.search.elastic.HapiElasticsearchAnalysisConfigurer"
|
||||
"spring.jpa.properties.hibernate.search.backend.hosts=localhost:9200",
|
||||
"spring.jpa.properties.hibernate.search.backend.protocol=http",
|
||||
"spring.jpa.properties.hibernate.search.backend.analysis.configurer=ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticsearchAnalysisConfigurer"
|
||||
})
|
||||
@ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class)
|
||||
class ElasticsearchLastNR4IT {
|
||||
@@ -73,26 +74,26 @@ class ElasticsearchLastNR4IT {
|
||||
public static ElasticsearchContainer embeddedElastic = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchSvcImpl myElasticsearchSvc;
|
||||
private ElasticsearchBootSvcImpl myElasticsearchSvc;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws IOException {
|
||||
//Given
|
||||
ElasticsearchClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(
|
||||
"http", embeddedElastic.getHost() + ":" + embeddedElastic.getMappedPort(9200), "", "");
|
||||
// ElasticsearchClient elasticsearchHighLevelRestClient = ElasticsearchRestClientFactory.createElasticsearchHighLevelRestClient(
|
||||
// "http", embeddedElastic.getHost() + ":" + embeddedElastic.getMappedPort(9200), "", "");
|
||||
|
||||
/* As of 2023-08-10, HAPI FHIR sets SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS to 50000
|
||||
which is in excess of elastic's default max_result_window. If MAX_SUBSCRIPTION_RESULTS is changed
|
||||
to a value <= 10000, the following will no longer be necessary. - dotasek
|
||||
*/
|
||||
|
||||
elasticsearchHighLevelRestClient.indices().putTemplate(t->{
|
||||
/* elasticsearchHighLevelRestClient.indices().putTemplate(t->{
|
||||
t.name("hapi_fhir_template");
|
||||
t.indexPatterns("*");
|
||||
t.settings(new IndexSettings.Builder().maxResultWindow(50000).build());
|
||||
return t;
|
||||
});
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@@ -103,7 +104,7 @@ class ElasticsearchLastNR4IT {
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
//@Test
|
||||
@Test
|
||||
void testLastN() throws IOException, InterruptedException {
|
||||
Thread.sleep(2000);
|
||||
|
||||
@@ -125,6 +126,7 @@ class ElasticsearchLastNR4IT {
|
||||
IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
myElasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
|
||||
Thread.sleep(2000);
|
||||
|
||||
Parameters output = ourClient.operation().onType(Observation.class).named("lastn")
|
||||
.withParameter(Parameters.class, "max", new IntegerType(1))
|
||||
@@ -154,8 +156,10 @@ 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=" + embeddedElastic.getHost() +":" + embeddedElastic.getMappedPort(9200))
|
||||
TestPropertyValues.of("spring.elasticsearch.uris=" + embeddedElastic.getHost() +":" + embeddedElastic.getMappedPort(9200))
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
TestPropertyValues.of("spring.jpa.properties.hibernate.search.backend.hosts=" + embeddedElastic.getHost() +":" + embeddedElastic.getMappedPort(9200))
|
||||
.applyTo(configurableApplicationContext.getEnvironment());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties =
|
||||
{
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr5_dbpm",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.fhir_version=r5",
|
||||
"hapi.fhir.partitioning.database_partition_mode_enabled=true",
|
||||
"hapi.fhir.partitioning.patient_id_partitioning_mode=true"
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
"hapi.fhir.fhir_version=dstu2",
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr2",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap"
|
||||
})
|
||||
class ExampleServerDstu2IT {
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
"hapi.fhir.allow_external_references=true",
|
||||
"hapi.fhir.allow_placeholder_references=true",
|
||||
"spring.main.allow-bean-definition-overriding=true"
|
||||
"spring.main.allow-bean-definition-overriding=true",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap"
|
||||
})
|
||||
class ExampleServerDstu3IT implements IServerSupport {
|
||||
|
||||
|
||||
@@ -17,16 +17,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
classes = {Application.class},
|
||||
properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4b",
|
||||
"hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4b",
|
||||
"hapi.fhir.subscription.websocket_enabled=false",
|
||||
"hapi.fhir.mdm_enabled=false",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
// 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"})
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4b",
|
||||
"hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.fhir_version=r4b",
|
||||
"hapi.fhir.subscription.websocket_enabled=false",
|
||||
"hapi.fhir.mdm_enabled=false",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
// 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"})
|
||||
class ExampleServerR4BIT {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4BIT.class);
|
||||
private IGenericClient ourClient;
|
||||
@@ -107,7 +108,6 @@ class ExampleServerR4BIT {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart;
|
||||
RepositoryConfig.class
|
||||
}, properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"spring.ai.mcp.server.enabled=false",
|
||||
"hapi.fhir.enable_repository_validating_interceptor=true",
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
@@ -70,6 +71,9 @@ import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart;
|
||||
// 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",
|
||||
"management.health.elasticsearch.enabled=false",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"management.endpoints.web.exposure.include=*",
|
||||
"hapi.fhir.remote_terminology_service.snomed.system=http://snomed.info/sct",
|
||||
"hapi.fhir.remote_terminology_service.snomed.url=https://tx.fhir.org/r4"
|
||||
})
|
||||
|
||||
@@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties =
|
||||
{
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr5",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.fhir_version=r5",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
|
||||
@@ -33,7 +33,7 @@ public class McpTests {
|
||||
|
||||
var fhirContext = FhirContext.forR4();
|
||||
|
||||
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + port).endpoint("/mcp/message").build();
|
||||
var transport = HttpClientStreamableHttpTransport.builder("http://localhost:" + port).endpoint("/mcp/messages").build();
|
||||
var client = McpClient.sync(transport).requestTimeout(Duration.ofSeconds(10)).capabilities(McpSchema.ClientCapabilities.builder().roots(true) // Enable roots capability
|
||||
.sampling().build()).build();
|
||||
var initializationResult = client.initialize();
|
||||
|
||||
@@ -8,10 +8,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.nickname.INicknameSvc;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = {
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.mdm_enabled=true"
|
||||
})
|
||||
@@ -19,9 +18,6 @@ class MdmTest {
|
||||
@Autowired
|
||||
INicknameSvc nicknameService;
|
||||
|
||||
@Autowired
|
||||
JpaStorageSettings jpaStorageSettings;
|
||||
|
||||
@Autowired
|
||||
SubscriptionSettings subscriptionSettings;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties =
|
||||
{
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4-mt",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap",
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.subscription.websocket_enabled=true",
|
||||
"hapi.fhir.cr_enabled=false",
|
||||
|
||||
@@ -30,7 +30,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:dbr4",
|
||||
"hapi.fhir.fhir_version=r4",
|
||||
"hapi.fhir.userRequestRetryVersionConflictsInterceptorEnabled=true"
|
||||
"hapi.fhir.userRequestRetryVersionConflictsInterceptorEnabled=true",
|
||||
"spring.jpa.properties.hibernate.search.backend.directory.type=local-heap"
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,83 +1,4 @@
|
||||
management:
|
||||
#The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus
|
||||
endpoints:
|
||||
enabled-by-default: false
|
||||
web:
|
||||
exposure:
|
||||
include: 'info,health,prometheus,metrics' # or '*' for all
|
||||
endpoint:
|
||||
info:
|
||||
enabled: true
|
||||
metrics:
|
||||
enabled: true
|
||||
health:
|
||||
enabled: true
|
||||
probes:
|
||||
enabled: true
|
||||
group:
|
||||
liveness:
|
||||
include:
|
||||
- livenessState
|
||||
- readinessState
|
||||
prometheus:
|
||||
enabled: true
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
spring:
|
||||
main:
|
||||
allow-circular-references: true
|
||||
allow-bean-definition-overriding: true
|
||||
flyway:
|
||||
enabled: false
|
||||
fail-on-missing-locations: false
|
||||
baselineOnMigrate: true
|
||||
datasource:
|
||||
url: jdbc:h2:mem:test_mem
|
||||
username: sa
|
||||
password: null
|
||||
driverClassName: org.h2.Driver
|
||||
max-active: 15
|
||||
|
||||
# database connection pool size
|
||||
hikari:
|
||||
maximum-pool-size: 10
|
||||
jpa:
|
||||
properties:
|
||||
hibernate.format_sql: false
|
||||
hibernate.show_sql: false
|
||||
|
||||
#########################################
|
||||
# Hibernate Dialect Setting
|
||||
#########################################
|
||||
# Use one of the following values:
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirDerbyDialect
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirOracleDialect
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirSQLServerDialect
|
||||
# ca.uhn.fhir.jpa.model.dialect.HapiFhirMySQLDialect (Deprecated!)
|
||||
#########################################
|
||||
hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect
|
||||
#########################################
|
||||
# hibernate.hbm2ddl.auto: update
|
||||
# hibernate.jdbc.batch_size: 20
|
||||
# hibernate.cache.use_query_cache: false
|
||||
# hibernate.cache.use_second_level_cache: false
|
||||
# hibernate.cache.use_structured_entries: false
|
||||
# hibernate.cache.use_minimal_puts: false
|
||||
### These settings will enable fulltext search with lucene or elastic
|
||||
hibernate.search.enabled: false
|
||||
### lucene parameters
|
||||
# hibernate.search.backend.type: lucene
|
||||
# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiLuceneAnalysisConfigurer
|
||||
# hibernate.search.backend.directory.type: local-filesystem
|
||||
# hibernate.search.backend.directory.root: target/lucenefiles
|
||||
# hibernate.search.backend.lucene_version: lucene_current
|
||||
### elastic parameters ===> see also elasticsearch section below <===
|
||||
# hibernate.search.backend.type: elasticsearch
|
||||
# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
|
||||
|
||||
hapi:
|
||||
fhir:
|
||||
Reference in New Issue
Block a user