Merge pull request #610 from hapifhir/cr-6.9.6-snapshot

6.10.0 release merge with Clinical Reasoning config
This commit is contained in:
Kevin Dougan SmileCDR
2023-11-28 13:30:18 -05:00
committed by GitHub
63 changed files with 175611 additions and 3897 deletions

View File

@@ -336,12 +336,12 @@ NOTE: MS SQL Server by default uses a case-insensitive codepage. This will cause
It is recommended to deploy a case-sensitive database prior to running HAPI FHIR when using MS SQL Server to avoid these and potentially other issues. It is recommended to deploy a case-sensitive database prior to running HAPI FHIR when using MS SQL Server to avoid these and potentially other issues.
## Adding custom interceptors ## Adding custom interceptors
Custom interceptors can be registered with the server by including the property `hapi.fhir.custom-interceptor-classes`. This will take a comma separated list of fully-qualified class names which will be registered with the server. Custom interceptors can be registered with the server by including the property `hapi.fhir.custom-interceptor-classes`. This will take a comma separated list of fully-qualified class names which will be registered with the server.
Interceptors will be discovered in one of two ways: Interceptors will be discovered in one of two ways:
1) discovered from the Spring application context as existing Beans (can be used in conjunction with `hapi.fhir.custom-bean-packages`) or registered with Spring via other methods 1) discovered from the Spring application context as existing Beans (can be used in conjunction with `hapi.fhir.custom-bean-packages`) or registered with Spring via other methods
or or
2) classes will be instantiated via reflection if no matching Bean is found 2) classes will be instantiated via reflection if no matching Bean is found

10
pom.xml
View File

@@ -131,6 +131,16 @@
<artifactId>hapi-fhir-jpaserver-mdm</artifactId> <artifactId>hapi-fhir-jpaserver-mdm</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- This dependency includes the CDS Hooks Server -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server-cds-hooks</artifactId>
<version>${project.version}</version>
</dependency>
<!-- This dependency includes the OpenAPI Server --> <!-- This dependency includes the OpenAPI Server -->
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>

View File

@@ -865,4 +865,4 @@ public class AppProperties {
public void setEnable_index_of_type(boolean enable_index_of_type) { public void setEnable_index_of_type(boolean enable_index_of_type) {
this.enable_index_of_type = enable_index_of_type; this.enable_index_of_type = enable_index_of_type;
} }
} }

View File

@@ -3,7 +3,10 @@ package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.batch2.jobs.config.Batch2JobsConfig; import ca.uhn.fhir.batch2.jobs.config.Batch2JobsConfig;
import ca.uhn.fhir.jpa.batch2.JpaBatch2Config; import ca.uhn.fhir.jpa.batch2.JpaBatch2Config;
import ca.uhn.fhir.jpa.starter.annotations.OnEitherVersion; import ca.uhn.fhir.jpa.starter.annotations.OnEitherVersion;
import ca.uhn.fhir.jpa.starter.cdshooks.StarterCdsHooksConfig;
import ca.uhn.fhir.jpa.starter.common.FhirTesterConfig; import ca.uhn.fhir.jpa.starter.common.FhirTesterConfig;
import ca.uhn.fhir.jpa.starter.cr.StarterCrDstu3Config;
import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config;
import ca.uhn.fhir.jpa.starter.mdm.MdmConfig; import ca.uhn.fhir.jpa.starter.mdm.MdmConfig;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
@@ -29,6 +32,9 @@ import org.springframework.web.servlet.DispatcherServlet;
@ServletComponentScan(basePackageClasses = {RestfulServer.class}) @ServletComponentScan(basePackageClasses = {RestfulServer.class})
@SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class, ThymeleafAutoConfiguration.class}) @SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class, ThymeleafAutoConfiguration.class})
@Import({ @Import({
StarterCrR4Config.class,
StarterCrDstu3Config.class,
StarterCdsHooksConfig.class,
SubscriptionSubmitterConfig.class, SubscriptionSubmitterConfig.class,
SubscriptionProcessorConfig.class, SubscriptionProcessorConfig.class,
SubscriptionChannelConfig.class, SubscriptionChannelConfig.class,
@@ -39,51 +45,54 @@ import org.springframework.web.servlet.DispatcherServlet;
}) })
public class Application extends SpringBootServletInitializer { public class Application extends SpringBootServletInitializer {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
//Server is now accessible at eg. http://localhost:8080/fhir/metadata // Server is now accessible at eg. http://localhost:8080/fhir/metadata
//UI is now accessible at http://localhost:8080/ // UI is now accessible at http://localhost:8080/
} }
@Override @Override
protected SpringApplicationBuilder configure( protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
SpringApplicationBuilder builder) { return builder.sources(Application.class);
return builder.sources(Application.class); }
}
@Autowired @Autowired
AutowireCapableBeanFactory beanFactory; AutowireCapableBeanFactory beanFactory;
@Bean @Bean
@Conditional(OnEitherVersion.class) @Conditional(OnEitherVersion.class)
public ServletRegistrationBean hapiServletRegistration(RestfulServer restfulServer) { public ServletRegistrationBean hapiServletRegistration(RestfulServer restfulServer) {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
beanFactory.autowireBean(restfulServer); beanFactory.autowireBean(restfulServer);
servletRegistrationBean.setServlet(restfulServer); servletRegistrationBean.setServlet(restfulServer);
servletRegistrationBean.addUrlMappings("/fhir/*"); servletRegistrationBean.addUrlMappings("/fhir/*");
servletRegistrationBean.setLoadOnStartup(1); servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean; return servletRegistrationBean;
} }
@Bean @Bean
public ServletRegistrationBean overlayRegistrationBean() { public ServletRegistrationBean overlayRegistrationBean() {
AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext =
annotationConfigWebApplicationContext.register(FhirTesterConfig.class); new AnnotationConfigWebApplicationContext();
annotationConfigWebApplicationContext.register(FhirTesterConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet( DispatcherServlet dispatcherServlet = new DispatcherServlet(annotationConfigWebApplicationContext);
annotationConfigWebApplicationContext); dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class);
dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class); dispatcherServlet.setContextConfigLocation(FhirTesterConfig.class.getName());
dispatcherServlet.setContextConfigLocation(FhirTesterConfig.class.getName());
ServletRegistrationBean registrationBean = new ServletRegistrationBean(); ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(dispatcherServlet); registrationBean.setServlet(dispatcherServlet);
registrationBean.addUrlMappings("/*"); registrationBean.addUrlMappings("/*");
registrationBean.setLoadOnStartup(1); registrationBean.setLoadOnStartup(1);
return registrationBean; return registrationBean;
}
} // @Bean
// IRepositoryFactory repositoryFactory(DaoRegistry theDaoRegistry, RestfulServer theRestfulServer) {
// return rd -> new HapiFhirRepository(theDaoRegistry, rd, theRestfulServer);
// }
} }

View File

@@ -19,33 +19,32 @@ public class ExtraStaticFilesConfigurer implements WebMvcConfigurer {
public ExtraStaticFilesConfigurer(AppProperties appProperties) { public ExtraStaticFilesConfigurer(AppProperties appProperties) {
rootContextPath = appProperties.getStaticLocationPrefix(); rootContextPath = appProperties.getStaticLocationPrefix();
if(rootContextPath.endsWith("/")) if (rootContextPath.endsWith("/"))
rootContextPath = rootContextPath.substring(0, rootContextPath.lastIndexOf('/')); rootContextPath = rootContextPath.substring(0, rootContextPath.lastIndexOf('/'));
staticLocation = appProperties.getStaticLocation(); staticLocation = appProperties.getStaticLocation();
if(staticLocation.endsWith("/")) if (staticLocation.endsWith("/")) staticLocation = staticLocation.substring(0, staticLocation.lastIndexOf('/'));
staticLocation = staticLocation.substring(0, staticLocation.lastIndexOf('/'));
} }
@Override
public void addResourceHandlers(ResourceHandlerRegistry theRegistry) {
theRegistry.addResourceHandler(rootContextPath + "/**").addResourceLocations(staticLocation);
}
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry theRegistry) { public void addViewControllers(ViewControllerRegistry registry) {
theRegistry.addResourceHandler(rootContextPath + "/**").addResourceLocations(staticLocation); String path = URI.create(staticLocation).getPath();
} String lastSegment = path.substring(path.lastIndexOf('/') + 1);
@Override registry.addViewController(rootContextPath)
public void addViewControllers(ViewControllerRegistry registry) { .setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html");
String path = URI.create(staticLocation).getPath();
String lastSegment = path.substring(path.lastIndexOf('/') + 1);
registry.addViewController(rootContextPath).setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html"); registry.addViewController(rootContextPath + "/*")
.setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html");
registry.addViewController(rootContextPath + "/*").setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html"); registry.addViewController(rootContextPath + "/" + lastSegment + "/")
.setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html");
registry.addViewController(rootContextPath + "/" + lastSegment + "/").setViewName("redirect:" + rootContextPath + "/" + lastSegment + "/index.html"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
registry.setOrder(Ordered.HIGHEST_PRECEDENCE); }
}
}

View File

@@ -10,9 +10,11 @@ public class OnCorsPresent implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
AppProperties config = Binder.get(conditionContext.getEnvironment()).bind("hapi.fhir", AppProperties.class).orElse(null); AppProperties config = Binder.get(conditionContext.getEnvironment())
.bind("hapi.fhir", AppProperties.class)
.orElse(null);
if (config == null) return false; if (config == null) return false;
if (config.getCors() == null) return false; if (config.getCors() == null) return false;
return true; return true;
} }
} }

View File

@@ -6,14 +6,13 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnDSTU2Condition implements Condition { public class OnDSTU2Condition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext. FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext
getEnvironment() .getEnvironment()
.getProperty("hapi.fhir.fhir_version") .getProperty("hapi.fhir.fhir_version")
.toUpperCase()); .toUpperCase());
return version == FhirVersionEnum.DSTU2; return version == FhirVersionEnum.DSTU2;
}
}
} }

View File

@@ -6,14 +6,13 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnDSTU3Condition implements Condition { public class OnDSTU3Condition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext. FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext
getEnvironment() .getEnvironment()
.getProperty("hapi.fhir.fhir_version") .getProperty("hapi.fhir.fhir_version")
.toUpperCase()); .toUpperCase());
return version == FhirVersionEnum.DSTU3; return version == FhirVersionEnum.DSTU3;
}
}
} }

View File

@@ -6,34 +6,28 @@ import org.springframework.context.annotation.Conditional;
public class OnEitherVersion extends AnyNestedCondition { public class OnEitherVersion extends AnyNestedCondition {
OnEitherVersion() { OnEitherVersion() {
super(ConfigurationPhase.REGISTER_BEAN); super(ConfigurationPhase.REGISTER_BEAN);
}
@Override
protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) {
ConditionOutcome result = super.getFinalMatchOutcome(memberOutcomes);
return result;
}
@Conditional(OnDSTU2Condition.class)
static class OnDSTU2 {
}
@Conditional(OnDSTU3Condition.class)
static class OnDSTU3 {
}
@Conditional(OnR4Condition.class)
static class OnR4 {
}
@Conditional(OnR4BCondition.class)
static class OnR4B {
} }
@Conditional(OnR5Condition.class) @Override
static class OnR5 { protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) {
} ConditionOutcome result = super.getFinalMatchOutcome(memberOutcomes);
return result;
}
} @Conditional(OnDSTU2Condition.class)
static class OnDSTU2 {}
@Conditional(OnDSTU3Condition.class)
static class OnDSTU3 {}
@Conditional(OnR4Condition.class)
static class OnR4 {}
@Conditional(OnR4BCondition.class)
static class OnR4B {}
@Conditional(OnR5Condition.class)
static class OnR5 {}
}

View File

@@ -10,9 +10,11 @@ public class OnImplementationGuidesPresent implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
AppProperties config = Binder.get(conditionContext.getEnvironment()).bind("hapi.fhir", AppProperties.class).orElse(null); AppProperties config = Binder.get(conditionContext.getEnvironment())
.bind("hapi.fhir", AppProperties.class)
.orElse(null);
if (config == null) return false; if (config == null) return false;
if (config.getImplementationGuides() == null) return false; if (config.getImplementationGuides() == null) return false;
return !config.getImplementationGuides().isEmpty(); return !config.getImplementationGuides().isEmpty();
} }
} }

View File

@@ -8,10 +8,10 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnR4BCondition implements Condition { public class OnR4BCondition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String version = conditionContext. String version = conditionContext
getEnvironment() .getEnvironment()
.getProperty("hapi.fhir.fhir_version") .getProperty("hapi.fhir.fhir_version")
.toUpperCase(); .toUpperCase();
return FhirVersionEnum.R4B.name().equals(version); return FhirVersionEnum.R4B.name().equals(version);
} }

View File

@@ -6,14 +6,13 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnR4Condition implements Condition { public class OnR4Condition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext. FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext
getEnvironment() .getEnvironment()
.getProperty("hapi.fhir.fhir_version") .getProperty("hapi.fhir.fhir_version")
.toUpperCase()); .toUpperCase());
return version == FhirVersionEnum.R4; return version == FhirVersionEnum.R4;
}
}
} }

View File

@@ -6,14 +6,13 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
public class OnR5Condition implements Condition { public class OnR5Condition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext. FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext
getEnvironment() .getEnvironment()
.getProperty("hapi.fhir.fhir_version") .getProperty("hapi.fhir.fhir_version")
.toUpperCase()); .toUpperCase());
return version == FhirVersionEnum.R5; return version == FhirVersionEnum.R5;
}
}
} }

View File

@@ -0,0 +1,14 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class CdsHooksConfigCondition implements Condition {
@Override
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.cdshooks.enabled");
return Boolean.parseBoolean(property);
}
}

View File

@@ -0,0 +1,27 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hapi.fhir.cdshooks")
public class CdsHooksProperties {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
private String clientIdHeaderName;
public String getClientIdHeaderName() {
return clientIdHeaderName;
}
public void setClientIdHeaderName(String clientIdHeaderName) {
this.clientIdHeaderName = clientIdHeaderName;
}
}

View File

@@ -0,0 +1,7 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties({"extension"})
public class CdsHooksRequest extends CdsServiceRequestJson {}

View File

@@ -0,0 +1,129 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceRequestJson;
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import java.io.IOException;
import java.util.stream.Collectors;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static ca.uhn.hapi.fhir.cdshooks.config.CdsHooksConfig.CDS_HOOKS_OBJECT_MAPPER_FACTORY;
@Configurable
public class CdsHooksServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class);
private static final long serialVersionUID = 1L;
@Autowired
private AppProperties appProperties;
@Autowired
private ProviderConfiguration providerConfiguration;
@Autowired
ICdsServiceRegistry cdsServiceRegistry;
@Autowired
RestfulServer restfulServer;
@Autowired
@Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY)
ObjectMapper objectMapper;
protected ProviderConfiguration getProviderConfiguration() {
return this.providerConfiguration;
}
// CORS Pre-flight
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) {
ErrorHandling.setAccessControlHeaders(resp, appProperties);
resp.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType());
resp.setHeader("X-Content-Type-Options", "nosniff");
resp.setStatus(HttpServletResponse.SC_OK);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.info(request.getRequestURI());
if (!request.getRequestURL().toString().endsWith("/cds-services")
&& !request.getRequestURL().toString().endsWith("/cds-services/")) {
logger.error(request.getRequestURI());
throw new ServletException("This servlet is not configured to handle GET requests.");
}
ErrorHandling.setAccessControlHeaders(response, appProperties);
response.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType());
response.getWriter()
.println(new GsonBuilder()
.setPrettyPrinting()
.create()
.toJson(JsonParser.parseString(objectMapper.writeValueAsString(getServices()))));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) {
throw new ServletException(String.format(
"Invalid content type %s. Please use application/json.", request.getContentType()));
}
logger.info(request.getRequestURI());
String service = request.getPathInfo().replace("/", "");
String requestJson = request.getReader().lines().collect(Collectors.joining());
CdsHooksRequest cdsHooksRequest = objectMapper.readValue(requestJson, CdsHooksRequest.class);
logRequestInfo(cdsHooksRequest, requestJson);
CdsServiceResponseJson serviceResponseJson = cdsServiceRegistry.callService(service, cdsHooksRequest);
// Using GSON pretty print format as Jackson's is ugly
String jsonResponse = new GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.create()
.toJson(JsonParser.parseString(objectMapper.writeValueAsString(serviceResponseJson)));
logger.info(jsonResponse);
response.setContentType("text/json;charset=UTF-8");
response.getWriter().println(jsonResponse);
} catch (BaseServerResponseException e) {
ErrorHandling.handleError(response, "ERROR: Exception connecting to remote server.", e, appProperties);
logger.error(e.toString());
} catch (Exception e) {
logger.error(e.toString());
throw new ServletException("ERROR: Exception in cds-hooks processing.", e);
}
}
private void logRequestInfo(CdsServiceRequestJson request, String jsonRequest) {
logger.info(jsonRequest);
logger.info("cds-hooks hook instance: {}", request.getHookInstance());
logger.info("cds-hooks local server address: {}", appProperties.getServer_address());
logger.info("cds-hooks fhir server address: {}", request.getFhirServer());
logger.info(
"cds-hooks cql_logging_enabled: {}",
this.getProviderConfiguration().getCqlLoggingEnabled());
}
private CdsServicesJson getServices() {
return cdsServiceRegistry.getCdsServicesJson();
}
}

View File

@@ -0,0 +1,105 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import javax.servlet.http.HttpServletResponse;
public class ErrorHandling {
private ErrorHandling() {}
public static void handleError(
HttpServletResponse response, String message, Exception e, AppProperties myAppProperties)
throws IOException {
setAccessControlHeaders(response, myAppProperties);
response.setStatus(500); // This will be overwritten with the correct status code downstream if needed.
response.getWriter().println(message);
printMessageAndCause(e, response);
if (e instanceof BaseServerResponseException) {
handleServerResponseException((BaseServerResponseException) e, response);
} else if (e.getCause() instanceof BaseServerResponseException) {
handleServerResponseException((BaseServerResponseException) e.getCause(), response);
}
printStackTrack(e, response);
}
private static void handleServerResponseException(BaseServerResponseException e, HttpServletResponse response)
throws IOException {
switch (e.getStatusCode()) {
case 401:
case 403:
response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode());
response.getWriter()
.println(
"Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access.");
response.setStatus(412);
break;
case 404:
response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode());
response.getWriter().println("Ensure the resource exists on the remote server.");
response.setStatus(412);
break;
default:
response.getWriter().println("Unhandled Error in Remote FHIR server: " + e.getStatusCode());
}
}
private static void printMessageAndCause(Exception e, HttpServletResponse response) throws IOException {
if (e.getMessage() != null) {
response.getWriter().println(e.getMessage());
}
if (e.getCause() != null && e.getCause().getMessage() != null) {
response.getWriter().println(e.getCause().getMessage());
}
}
private static void printStackTrack(Exception e, HttpServletResponse response) throws IOException {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String exceptionAsString = sw.toString();
response.getWriter().println(exceptionAsString);
}
public static void setAccessControlHeaders(HttpServletResponse resp, AppProperties myAppProperties) {
if (myAppProperties.getCors() != null) {
if (myAppProperties.getCors().getAllow_Credentials()) {
resp.setHeader(
"Access-Control-Allow-Origin",
myAppProperties.getCors().getAllowed_origin().stream()
.findFirst()
.get());
resp.setHeader(
"Access-Control-Allow-Methods",
String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS")));
resp.setHeader(
"Access-Control-Allow-Headers",
String.join(
", ",
Arrays.asList(
"x-fhir-starter",
"Origin",
"Accept",
"X-Requested-With",
"Content-Type",
"Authorization",
"Cache-Control")));
resp.setHeader(
"Access-Control-Expose-Headers",
String.join(", ", Arrays.asList("Location", "Content-Location")));
resp.setHeader("Access-Control-Max-Age", "86400");
}
}
}
public static class CdsHooksError extends RuntimeException {
public CdsHooksError(String message) {
super(message);
}
}
}

View File

@@ -0,0 +1,30 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.fhir.jpa.starter.cr.CrProperties;
public class ProviderConfiguration {
public static final ProviderConfiguration DEFAULT_PROVIDER_CONFIGURATION =
new ProviderConfiguration(false, "client_id");
private final String clientIdHeaderName;
private final boolean cqlLoggingEnabled;
public ProviderConfiguration(boolean cqlLoggingEnabled, String clientIdHeaderName) {
this.cqlLoggingEnabled = cqlLoggingEnabled;
this.clientIdHeaderName = clientIdHeaderName;
}
public ProviderConfiguration(CdsHooksProperties cdsProperties, CrProperties crProperties) {
this.clientIdHeaderName = cdsProperties.getClientIdHeaderName();
this.cqlLoggingEnabled = crProperties.isCqlRuntimeDebugLoggingEnabled();
}
public String getClientIdHeaderName() {
return this.clientIdHeaderName;
}
public boolean getCqlLoggingEnabled() {
return this.cqlLoggingEnabled;
}
}

View File

@@ -0,0 +1,66 @@
package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.fhir.jpa.starter.cr.CrConfigCondition;
import ca.uhn.fhir.jpa.starter.cr.CrProperties;
import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc;
import ca.uhn.hapi.fhir.cdshooks.config.CdsHooksConfig;
import ca.uhn.hapi.fhir.cdshooks.svc.CdsHooksContextBooter;
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrSettings;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Conditional({CdsHooksConfigCondition.class, CrConfigCondition.class})
@Import(CdsHooksConfig.class)
public class StarterCdsHooksConfig {
@Bean
public CdsHooksProperties cdsHooksProperties() {
return new CdsHooksProperties();
}
@Bean
public CdsCrSettings cdsCrSettings(CdsHooksProperties cdsHooksProperties) {
CdsCrSettings settings = CdsCrSettings.getDefault();
settings.setClientIdHeaderName(cdsHooksProperties.getClientIdHeaderName());
return settings;
}
@Bean
public CdsHooksContextBooter cdsHooksContextBooter() {
// ourLog.info("No Spring Context provided. Assuming all CDS Services will be registered dynamically.");
return new CdsHooksContextBooter();
}
public static class CdsHooksDaoAuthorizationSvc implements ICdsHooksDaoAuthorizationSvc {
@Override
public void authorizePreShow(IBaseResource theResource) {}
}
@Bean
public ProviderConfiguration providerConfiguration(CdsHooksProperties cdsProperties, CrProperties crProperties) {
return new ProviderConfiguration(cdsProperties, crProperties);
}
@Bean
ICdsHooksDaoAuthorizationSvc cdsHooksDaoAuthorizationSvc() {
return new CdsHooksDaoAuthorizationSvc();
}
@Bean
public ServletRegistrationBean<CdsHooksServlet> cdsHooksRegistrationBean(AutowireCapableBeanFactory beanFactory) {
CdsHooksServlet cdsHooksServlet = new CdsHooksServlet();
beanFactory.autowireBean(cdsHooksServlet);
ServletRegistrationBean<CdsHooksServlet> registrationBean = new ServletRegistrationBean<>();
registrationBean.setName("cds-hooks servlet");
registrationBean.setServlet(cdsHooksServlet);
registrationBean.addUrlMappings("/cds-services/*");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}

View File

@@ -10,18 +10,20 @@ import org.springframework.core.env.ConfigurableEnvironment;
@Configuration @Configuration
public class ElasticsearchConfig { public class ElasticsearchConfig {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElasticsearchConfig.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElasticsearchConfig.class);
@Bean @Bean
public ElasticsearchSvcImpl elasticsearchSvc(ConfigurableEnvironment configurableEnvironment) { public ElasticsearchSvcImpl elasticsearchSvc(ConfigurableEnvironment configurableEnvironment) {
if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) { if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) {
String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment); String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment);
if (elasticsearchUrl.startsWith("http")) { if (elasticsearchUrl.startsWith("http")) {
elasticsearchUrl =elasticsearchUrl.substring(elasticsearchUrl.indexOf("://") + 3); elasticsearchUrl = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://") + 3);
} }
String elasticsearchProtocol = EnvironmentHelper.getElasticsearchServerProtocol(configurableEnvironment); String elasticsearchProtocol = EnvironmentHelper.getElasticsearchServerProtocol(configurableEnvironment);
String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment); String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment);
String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment); String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment);
ourLog.info("Configuring elasticsearch {} {}", elasticsearchProtocol, elasticsearchUrl); ourLog.info("Configuring elasticsearch {} {}", elasticsearchProtocol, elasticsearchUrl);
return new ElasticsearchSvcImpl(elasticsearchProtocol, elasticsearchUrl, elasticsearchUsername, elasticsearchPassword); return new ElasticsearchSvcImpl(
elasticsearchProtocol, elasticsearchUrl, elasticsearchUsername, elasticsearchPassword);
} else { } else {
return null; return null;
} }

View File

@@ -15,7 +15,6 @@ import ca.uhn.fhir.rest.server.mail.MailConfig;
import ca.uhn.fhir.rest.server.mail.MailSvc; import ca.uhn.fhir.rest.server.mail.MailSvc;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -24,6 +23,7 @@ import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.HashSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -33,210 +33,239 @@ import java.util.stream.Collectors;
@EnableTransactionManagement @EnableTransactionManagement
public class FhirServerConfigCommon { public class FhirServerConfigCommon {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class);
public FhirServerConfigCommon(AppProperties appProperties) {
ourLog.info("Server configured to " + (appProperties.getAllow_contains_searches() ? "allow" : "deny")
+ " contains searches");
ourLog.info("Server configured to " + (appProperties.getAllow_multiple_delete() ? "allow" : "deny")
+ " multiple deletes");
ourLog.info("Server configured to " + (appProperties.getAllow_external_references() ? "allow" : "deny")
+ " external references");
ourLog.info("Server configured to " + (appProperties.getDao_scheduling_enabled() ? "enable" : "disable")
+ " DAO scheduling");
ourLog.info("Server configured to " + (appProperties.getDelete_expunge_enabled() ? "enable" : "disable")
+ " delete expunges");
ourLog.info(
"Server configured to " + (appProperties.getExpunge_enabled() ? "enable" : "disable") + " expunges");
ourLog.info(
"Server configured to " + (appProperties.getAllow_override_default_search_params() ? "allow" : "deny")
+ " overriding default search params");
ourLog.info("Server configured to "
+ (appProperties.getAuto_create_placeholder_reference_targets() ? "allow" : "disable")
+ " auto-creating placeholder references");
ourLog.info(
"Server configured to auto-version references at paths {}",
appProperties.getAuto_version_reference_at_paths());
public FhirServerConfigCommon(AppProperties appProperties) { if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("Server configured to " + (appProperties.getAllow_contains_searches() ? "allow" : "deny") + " contains searches"); AppProperties.Subscription.Email email =
ourLog.info("Server configured to " + (appProperties.getAllow_multiple_delete() ? "allow" : "deny") + " multiple deletes"); appProperties.getSubscription().getEmail();
ourLog.info("Server configured to " + (appProperties.getAllow_external_references() ? "allow" : "deny") + " external references"); ourLog.info("Server is configured to enable email with host '" + email.getHost() + "' and port "
ourLog.info("Server configured to " + (appProperties.getDao_scheduling_enabled() ? "enable" : "disable") + " DAO scheduling"); + email.getPort());
ourLog.info("Server configured to " + (appProperties.getDelete_expunge_enabled() ? "enable" : "disable") + " delete expunges"); ourLog.info("Server will use '" + email.getFrom() + "' as the from email address");
ourLog.info("Server configured to " + (appProperties.getExpunge_enabled() ? "enable" : "disable") + " expunges");
ourLog.info("Server configured to " + (appProperties.getAllow_override_default_search_params() ? "allow" : "deny") + " overriding default search params");
ourLog.info("Server configured to " + (appProperties.getAuto_create_placeholder_reference_targets() ? "allow" : "disable") + " auto-creating placeholder references");
ourLog.info("Server configured to auto-version references at paths {}", appProperties.getAuto_version_reference_at_paths());
if (appProperties.getSubscription().getEmail() != null) { if (!Strings.isNullOrEmpty(email.getUsername())) {
AppProperties.Subscription.Email email = appProperties.getSubscription().getEmail(); ourLog.info("Server is configured to use username '" + email.getUsername() + "' for email");
ourLog.info("Server is configured to enable email with host '" + email.getHost() + "' and port " + email.getPort()); }
ourLog.info("Server will use '" + email.getFrom() + "' as the from email address");
if (!Strings.isNullOrEmpty(email.getUsername())) { if (!Strings.isNullOrEmpty(email.getPassword())) {
ourLog.info("Server is configured to use username '" + email.getUsername() + "' for email"); ourLog.info("Server is configured to use a password for email");
} }
}
if (!Strings.isNullOrEmpty(email.getPassword())) { if (appProperties.getSubscription().getResthook_enabled()) {
ourLog.info("Server is configured to use a password for email"); ourLog.info("REST-hook subscriptions enabled");
} }
}
if (appProperties.getSubscription().getResthook_enabled()) { if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("REST-hook subscriptions enabled"); ourLog.info("Email subscriptions enabled");
} }
if (appProperties.getSubscription().getEmail() != null) { if (appProperties.getEnable_index_contained_resource() == Boolean.TRUE) {
ourLog.info("Email subscriptions enabled"); ourLog.info("Indexed on contained resource enabled");
} }
}
if (appProperties.getEnable_index_contained_resource() == Boolean.TRUE) { /**
ourLog.info("Indexed on contained resource enabled"); * Configure FHIR properties around the the JPA server via this bean
} */
} @Bean
public JpaStorageSettings jpaStorageSettings(AppProperties appProperties) {
JpaStorageSettings jpaStorageSettings = new JpaStorageSettings();
/** jpaStorageSettings.setIndexMissingFields(
* Configure FHIR properties around the the JPA server via this bean appProperties.getEnable_index_missing_fields()
*/ ? StorageSettings.IndexEnabledEnum.ENABLED
@Bean : StorageSettings.IndexEnabledEnum.DISABLED);
public JpaStorageSettings jpaStorageSettings(AppProperties appProperties) { jpaStorageSettings.setAutoCreatePlaceholderReferenceTargets(
JpaStorageSettings jpaStorageSettings = new JpaStorageSettings(); appProperties.getAuto_create_placeholder_reference_targets());
jpaStorageSettings.setAutoVersionReferenceAtPaths(appProperties.getAuto_version_reference_at_paths());
jpaStorageSettings.setEnforceReferentialIntegrityOnWrite(
appProperties.getEnforce_referential_integrity_on_write());
jpaStorageSettings.setEnforceReferentialIntegrityOnDelete(
appProperties.getEnforce_referential_integrity_on_delete());
jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches());
jpaStorageSettings.setAllowMultipleDelete(appProperties.getAllow_multiple_delete());
jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
jpaStorageSettings.setSchedulingDisabled(!appProperties.getDao_scheduling_enabled());
jpaStorageSettings.setDeleteExpungeEnabled(appProperties.getDelete_expunge_enabled());
jpaStorageSettings.setExpungeEnabled(appProperties.getExpunge_enabled());
if (appProperties.getSubscription() != null
&& appProperties.getSubscription().getEmail() != null)
jpaStorageSettings.setEmailFromAddress(
appProperties.getSubscription().getEmail().getFrom());
jpaStorageSettings.setIndexMissingFields(appProperties.getEnable_index_missing_fields() ? StorageSettings.IndexEnabledEnum.ENABLED : StorageSettings.IndexEnabledEnum.DISABLED); Integer maxFetchSize = appProperties.getMax_page_size();
jpaStorageSettings.setAutoCreatePlaceholderReferenceTargets(appProperties.getAuto_create_placeholder_reference_targets()); jpaStorageSettings.setFetchSizeDefaultMaximum(maxFetchSize);
jpaStorageSettings.setAutoVersionReferenceAtPaths(appProperties.getAuto_version_reference_at_paths()); ourLog.info("Server configured to have a maximum fetch size of "
jpaStorageSettings.setEnforceReferentialIntegrityOnWrite(appProperties.getEnforce_referential_integrity_on_write()); + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize));
jpaStorageSettings.setEnforceReferentialIntegrityOnDelete(appProperties.getEnforce_referential_integrity_on_delete());
jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches());
jpaStorageSettings.setAllowMultipleDelete(appProperties.getAllow_multiple_delete());
jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
jpaStorageSettings.setSchedulingDisabled(!appProperties.getDao_scheduling_enabled());
jpaStorageSettings.setDeleteExpungeEnabled(appProperties.getDelete_expunge_enabled());
jpaStorageSettings.setExpungeEnabled(appProperties.getExpunge_enabled());
if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null)
jpaStorageSettings.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom());
Integer maxFetchSize = appProperties.getMax_page_size(); Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis();
jpaStorageSettings.setFetchSizeDefaultMaximum(maxFetchSize); jpaStorageSettings.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis);
ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis);
Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis(); Long retainCachedSearchesMinutes = appProperties.getRetain_cached_searches_mins();
jpaStorageSettings.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); jpaStorageSettings.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000);
ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis);
if (appProperties.getSubscription() != null) {
// Subscriptions are enabled by channel type
if (appProperties.getSubscription().getResthook_enabled()) {
ourLog.info("Enabling REST-hook subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(
org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK);
}
if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("Enabling email subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(
org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL);
}
if (appProperties.getSubscription().getWebsocket_enabled()) {
ourLog.info("Enabling websocket subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(
org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET);
}
}
Long retainCachedSearchesMinutes = appProperties.getRetain_cached_searches_mins(); jpaStorageSettings.setFilterParameterEnabled(appProperties.getFilter_search_enabled());
jpaStorageSettings.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000); jpaStorageSettings.setAdvancedHSearchIndexing(appProperties.getAdvanced_lucene_indexing());
jpaStorageSettings.setTreatBaseUrlsAsLocal(new HashSet<>(appProperties.getLocal_base_urls()));
if(appProperties.getSubscription() != null) { if (appProperties.getLastn_enabled()) {
// Subscriptions are enabled by channel type jpaStorageSettings.setLastNEnabled(true);
if (appProperties.getSubscription().getResthook_enabled()) { }
ourLog.info("Enabling REST-hook subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK);
}
if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("Enabling email subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL);
}
if (appProperties.getSubscription().getWebsocket_enabled()) {
ourLog.info("Enabling websocket subscriptions");
jpaStorageSettings.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET);
}
}
jpaStorageSettings.setFilterParameterEnabled(appProperties.getFilter_search_enabled()); if (appProperties.getInline_resource_storage_below_size() != 0) {
jpaStorageSettings.setAdvancedHSearchIndexing(appProperties.getAdvanced_lucene_indexing()); jpaStorageSettings.setInlineResourceTextBelowSize(appProperties.getInline_resource_storage_below_size());
jpaStorageSettings.setTreatBaseUrlsAsLocal(appProperties.getLocal_base_urls()); }
if (appProperties.getLastn_enabled()) { jpaStorageSettings.setStoreResourceInHSearchIndex(appProperties.getStore_resource_in_lucene_index_enabled());
jpaStorageSettings.setLastNEnabled(true); jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level());
} jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource());
if(appProperties.getInline_resource_storage_below_size() != 0){ if (appProperties.getAllowed_bundle_types() != null) {
jpaStorageSettings.setInlineResourceTextBelowSize(appProperties.getInline_resource_storage_below_size()); jpaStorageSettings.setBundleTypesAllowedForStorage(appProperties.getAllowed_bundle_types().stream()
} .map(BundleType::toCode)
.collect(Collectors.toSet()));
}
jpaStorageSettings.setStoreResourceInHSearchIndex(appProperties.getStore_resource_in_lucene_index_enabled()); jpaStorageSettings.setDeferIndexingForCodesystemsOfSize(
jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level()); appProperties.getDefer_indexing_for_codesystems_of_size());
jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource());
if (appProperties.getClient_id_strategy() == JpaStorageSettings.ClientIdStrategyEnum.ANY) {
jpaStorageSettings.setResourceServerIdStrategy(JpaStorageSettings.IdStrategyEnum.UUID);
jpaStorageSettings.setResourceClientIdStrategy(appProperties.getClient_id_strategy());
}
// Parallel Batch GET execution settings
jpaStorageSettings.setBundleBatchPoolSize(appProperties.getBundle_batch_pool_size());
jpaStorageSettings.setBundleBatchPoolSize(appProperties.getBundle_batch_pool_max_size());
storageSettings(appProperties, jpaStorageSettings);
return jpaStorageSettings;
}
if (appProperties.getAllowed_bundle_types() != null) { @Bean
jpaStorageSettings.setBundleTypesAllowedForStorage(appProperties.getAllowed_bundle_types().stream().map(BundleType::toCode).collect(Collectors.toSet())); public YamlPropertySourceLoader yamlPropertySourceLoader() {
} return new YamlPropertySourceLoader();
}
jpaStorageSettings.setDeferIndexingForCodesystemsOfSize(appProperties.getDefer_indexing_for_codesystems_of_size()); @Bean
public PartitionSettings partitionSettings(AppProperties appProperties) {
PartitionSettings retVal = new PartitionSettings();
// Partitioning
if (appProperties.getPartitioning() != null) {
retVal.setPartitioningEnabled(true);
retVal.setIncludePartitionInSearchHashes(
appProperties.getPartitioning().getPartitioning_include_in_search_hashes());
if (appProperties.getPartitioning().getAllow_references_across_partitions()) {
retVal.setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
} else {
retVal.setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode.NOT_ALLOWED);
}
}
if (appProperties.getClient_id_strategy() == JpaStorageSettings.ClientIdStrategyEnum.ANY) { return retVal;
jpaStorageSettings.setResourceServerIdStrategy(JpaStorageSettings.IdStrategyEnum.UUID); }
jpaStorageSettings.setResourceClientIdStrategy(appProperties.getClient_id_strategy());
}
//Parallel Batch GET execution settings
jpaStorageSettings.setBundleBatchPoolSize(appProperties.getBundle_batch_pool_size());
jpaStorageSettings.setBundleBatchPoolSize(appProperties.getBundle_batch_pool_max_size());
storageSettings(appProperties, jpaStorageSettings); @Primary
return jpaStorageSettings; @Bean
} public HibernatePropertiesProvider jpaStarterDialectProvider(
LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
return new JpaHibernatePropertiesProvider(myEntityManagerFactory);
}
@Bean protected StorageSettings storageSettings(AppProperties appProperties, JpaStorageSettings jpaStorageSettings) {
public YamlPropertySourceLoader yamlPropertySourceLoader() { jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches());
return new YamlPropertySourceLoader(); jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
} jpaStorageSettings.setDefaultSearchParamsCanBeOverridden(
appProperties.getAllow_override_default_search_params());
if (appProperties.getSubscription() != null
&& appProperties.getSubscription().getEmail() != null)
jpaStorageSettings.setEmailFromAddress(
appProperties.getSubscription().getEmail().getFrom());
@Bean jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level());
public PartitionSettings partitionSettings(AppProperties appProperties) {
PartitionSettings retVal = new PartitionSettings();
// Partitioning jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource());
if (appProperties.getPartitioning() != null) { jpaStorageSettings.setIndexIdentifierOfType(appProperties.getEnable_index_of_type());
retVal.setPartitioningEnabled(true); return jpaStorageSettings;
retVal.setIncludePartitionInSearchHashes(appProperties.getPartitioning().getPartitioning_include_in_search_hashes()); }
if(appProperties.getPartitioning().getAllow_references_across_partitions()) {
retVal.setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode.ALLOWED_UNQUALIFIED);
} else {
retVal.setAllowReferencesAcrossPartitions(CrossPartitionReferenceMode.NOT_ALLOWED);
}
}
return retVal; @Lazy
} @Bean
public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) {
DatabaseBlobBinaryStorageSvcImpl binaryStorageSvc = new DatabaseBlobBinaryStorageSvcImpl();
if (appProperties.getMax_binary_size() != null) {
binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size());
}
@Primary return binaryStorageSvc;
@Bean }
public HibernatePropertiesProvider jpaStarterDialectProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
return new JpaHibernatePropertiesProvider(myEntityManagerFactory);
}
@Bean
public IEmailSender emailSender(AppProperties appProperties) {
if (appProperties.getSubscription() != null
&& appProperties.getSubscription().getEmail() != null) {
protected StorageSettings storageSettings(AppProperties appProperties, JpaStorageSettings jpaStorageSettings) { return buildEmailSender(appProperties.getSubscription().getEmail());
jpaStorageSettings.setAllowContainsSearches(appProperties.getAllow_contains_searches()); }
jpaStorageSettings.setAllowExternalReferences(appProperties.getAllow_external_references());
jpaStorageSettings.setDefaultSearchParamsCanBeOverridden(appProperties.getAllow_override_default_search_params());
if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null)
jpaStorageSettings.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom());
jpaStorageSettings.setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level()); // Return a dummy anonymous function instead of null. Spring does not like null beans.
// TODO Get the signature of
jpaStorageSettings.setIndexOnContainedResources(appProperties.getEnable_index_contained_resource()); // ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory
jpaStorageSettings.setIndexIdentifierOfType(appProperties.getEnable_index_of_type()); // changed so it does not require an instance of an IEmailSender
return jpaStorageSettings; return theDetails -> {};
} }
@Lazy
@Bean
public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) {
DatabaseBlobBinaryStorageSvcImpl binaryStorageSvc = new DatabaseBlobBinaryStorageSvcImpl();
if (appProperties.getMax_binary_size() != null) {
binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size());
}
return binaryStorageSvc;
}
@Bean
public IEmailSender emailSender(AppProperties appProperties) {
if (appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) {
return buildEmailSender(appProperties.getSubscription().getEmail());
}
// Return a dummy anonymous function instead of null. Spring does not like null beans.
// TODO Get the signature of ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory
// changed so it does not require an instance of an IEmailSender
return theDetails -> {};
}
private static IEmailSender buildEmailSender(AppProperties.Subscription.Email email) { private static IEmailSender buildEmailSender(AppProperties.Subscription.Email email) {
return new EmailSenderImpl(new MailSvc(new MailConfig() return new EmailSenderImpl(new MailSvc(new MailConfig()
.setSmtpHostname(email.getHost()) .setSmtpHostname(email.getHost())
.setSmtpPort(email.getPort()) .setSmtpPort(email.getPort())
.setSmtpUsername(email.getUsername()) .setSmtpUsername(email.getUsername())
.setSmtpPassword(email.getPassword()) .setSmtpPassword(email.getPassword())
.setSmtpUseStartTLS(email.getStartTlsEnable()))); .setSmtpUseStartTLS(email.getStartTlsEnable())));
} }
} }

View File

@@ -13,14 +13,11 @@ import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Conditional(OnDSTU2Condition.class) @Conditional(OnDSTU2Condition.class)
@Import({ @Import({JpaDstu2Config.class, StarterJpaConfig.class})
JpaDstu2Config.class,
StarterJpaConfig.class
})
public class FhirServerConfigDstu2 { public class FhirServerConfigDstu2 {
@Bean @Bean
public ITermLoaderSvc termLoaderService(ITermDeferredStorageSvc theDeferredStorageSvc, ITermCodeSystemStorageSvc theCodeSystemStorageSvc) { public ITermLoaderSvc termLoaderService(
ITermDeferredStorageSvc theDeferredStorageSvc, ITermCodeSystemStorageSvc theCodeSystemStorageSvc) {
return new TermLoaderSvcImpl(theDeferredStorageSvc, theCodeSystemStorageSvc); return new TermLoaderSvcImpl(theDeferredStorageSvc, theCodeSystemStorageSvc);
} }
} }

View File

@@ -9,10 +9,5 @@ import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Conditional(OnDSTU3Condition.class) @Conditional(OnDSTU3Condition.class)
@Import({ @Import({JpaDstu3Config.class, StarterJpaConfig.class, StarterCrDstu3Config.class, ElasticsearchConfig.class})
JpaDstu3Config.class, public class FhirServerConfigDstu3 {}
StarterJpaConfig.class,
StarterCrDstu3Config.class,
ElasticsearchConfig.class})
public class FhirServerConfigDstu3 {
}

View File

@@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition;
import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config; import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config;
import ca.uhn.fhir.jpa.starter.ips.StarterIpsConfig; import ca.uhn.fhir.jpa.starter.ips.StarterIpsConfig;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@@ -18,5 +17,4 @@ import org.springframework.context.annotation.Import;
ElasticsearchConfig.class, ElasticsearchConfig.class,
StarterIpsConfig.class StarterIpsConfig.class
}) })
public class FhirServerConfigR4 { public class FhirServerConfigR4 {}
}

View File

@@ -9,11 +9,5 @@ import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Conditional(OnR4BCondition.class) @Conditional(OnR4BCondition.class)
@Import({ @Import({JpaR4BConfig.class, SubscriptionTopicConfig.class, StarterJpaConfig.class, ElasticsearchConfig.class})
JpaR4BConfig.class, public class FhirServerConfigR4B {}
SubscriptionTopicConfig.class,
StarterJpaConfig.class,
ElasticsearchConfig.class
})
public class FhirServerConfigR4B {
}

View File

@@ -9,11 +9,5 @@ import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Conditional(OnR5Condition.class) @Conditional(OnR5Condition.class)
@Import({ @Import({StarterJpaConfig.class, JpaR5Config.class, SubscriptionTopicConfig.class, ElasticsearchConfig.class})
StarterJpaConfig.class, public class FhirServerConfigR5 {}
JpaR5Config.class,
SubscriptionTopicConfig.class,
ElasticsearchConfig.class
})
public class FhirServerConfigR5 {
}

View File

@@ -4,11 +4,10 @@ import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.to.FhirTesterMvcConfig; import ca.uhn.fhir.to.FhirTesterMvcConfig;
import ca.uhn.fhir.to.TesterConfig; import ca.uhn.fhir.to.TesterConfig;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
//@formatter:off // @formatter:off
/** /**
* This spring config file configures the web testing module. It serves two * This spring config file configures the web testing module. It serves two
* purposes: * purposes:
@@ -19,7 +18,7 @@ import org.springframework.context.annotation.Import;
*/ */
@Configuration @Configuration
@Import(FhirTesterMvcConfig.class) @Import(FhirTesterMvcConfig.class)
@Conditional(FhirTesterConfigCondition.class) // @Conditional(FhirTesterConfigCondition.class)
public class FhirTesterConfig { public class FhirTesterConfig {
/** /**
@@ -36,22 +35,18 @@ public class FhirTesterConfig {
* deploying your server to a place with a fully qualified domain name, * deploying your server to a place with a fully qualified domain name,
* you might want to use that instead of using the variable. * you might want to use that instead of using the variable.
*/ */
@Bean @Bean
public TesterConfig testerConfig(AppProperties appProperties) { public TesterConfig testerConfig(AppProperties appProperties) {
TesterConfig retVal = new TesterConfig(); TesterConfig retVal = new TesterConfig();
appProperties.getTester().forEach((key, value) -> { appProperties.getTester().forEach((key, value) -> {
retVal retVal.addServer()
.addServer() .withId(key)
.withId(key) .withFhirVersion(value.getFhir_version())
.withFhirVersion(value.getFhir_version()) .withBaseUrl(value.getServer_address())
.withBaseUrl(value.getServer_address()) .withName(value.getName());
.withName(value.getName()); retVal.setRefuseToFetchThirdPartyUrls(value.getRefuse_to_fetch_third_party_urls());
retVal.setRefuseToFetchThirdPartyUrls( });
value.getRefuse_to_fetch_third_party_urls()); return retVal;
}
});
return retVal;
}
} }
//@formatter:on // @formatter:on

View File

@@ -9,8 +9,9 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
public class FhirTesterConfigCondition implements Condition { public class FhirTesterConfigCondition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
var properties = EnvironmentHelper.getPropertiesStartingWith((ConfigurableEnvironment) conditionContext.getEnvironment(), "hapi.fhir.tester"); var properties = EnvironmentHelper.getPropertiesStartingWith(
(ConfigurableEnvironment) conditionContext.getEnvironment(), "hapi.fhir.tester");
return !properties.isEmpty(); return !properties.isEmpty();
} }
} }

View File

@@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.starter.common; package ca.uhn.fhir.jpa.starter.common;
import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry; import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry;
import ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider;
import ca.uhn.fhir.batch2.jobs.imprt.BulkDataImportProvider; import ca.uhn.fhir.batch2.jobs.imprt.BulkDataImportProvider;
import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters; import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters;
import ca.uhn.fhir.batch2.jobs.reindex.ReindexProvider; import ca.uhn.fhir.batch2.jobs.reindex.ReindexProvider;
@@ -9,7 +10,6 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.IDaoRegistry; import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
@@ -18,7 +18,6 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.binary.interceptor.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binary.interceptor.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider; import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider;
import ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil; import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil; import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil;
import ca.uhn.fhir.jpa.config.util.ValidationSupportConfigUtil; import ca.uhn.fhir.jpa.config.util.ValidationSupportConfigUtil;
@@ -34,8 +33,8 @@ import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; import ca.uhn.fhir.jpa.packages.PackageInstallationSpec;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.provider.*;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
@@ -43,8 +42,8 @@ import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.jpa.starter.annotations.OnCorsPresent; import ca.uhn.fhir.jpa.starter.annotations.OnCorsPresent;
import ca.uhn.fhir.jpa.starter.annotations.OnImplementationGuidesPresent; import ca.uhn.fhir.jpa.starter.annotations.OnImplementationGuidesPresent;
import ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory; import ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory;
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider; 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.subscription.util.SubscriptionDebugLogInterceptor;
import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
@@ -53,9 +52,9 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.narrative2.NullNarrativeGenerator; import ca.uhn.fhir.narrative2.NullNarrativeGenerator;
import ca.uhn.fhir.rest.api.IResourceSupportedSvc; import ca.uhn.fhir.rest.api.IResourceSupportedSvc;
import ca.uhn.fhir.rest.openapi.OpenApiInterceptor; import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.*; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.interceptor.*; import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
@@ -75,18 +74,16 @@ import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import java.util.*;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.*;
import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR;
@Configuration @Configuration
//allow users to configure custom packages to scan for additional beans // allow users to configure custom packages to scan for additional beans
@ComponentScan(basePackages = { "${hapi.fhir.custom-bean-packages:}" }) @ComponentScan(basePackages = {"${hapi.fhir.custom-bean-packages:}"})
@Import( @Import(ThreadPoolFactoryConfig.class)
ThreadPoolFactoryConfig.class
)
public class StarterJpaConfig { public class StarterJpaConfig {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StarterJpaConfig.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StarterJpaConfig.class);
@@ -107,12 +104,9 @@ public class StarterJpaConfig {
return ValidationSupportConfigUtil.newCachingValidationSupport(theJpaValidationSupportChain); return ValidationSupportConfigUtil.newCachingValidationSupport(theJpaValidationSupportChain);
} }
@Autowired @Autowired
private ConfigurableEnvironment configurableEnvironment; private ConfigurableEnvironment configurableEnvironment;
/** /**
* Customize the default/max page sizes for search results. You can set these however * 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. * you want, although very large page sizes will require a lot of RAM.
@@ -125,7 +119,6 @@ public class StarterJpaConfig {
return pagingProvider; return pagingProvider;
} }
@Bean @Bean
public IResourceSupportedSvc resourceSupportedSvc(IDaoRegistry theDaoRegistry) { public IResourceSupportedSvc resourceSupportedSvc(IDaoRegistry theDaoRegistry) {
return new DaoRegistryResourceSupportedSvc(theDaoRegistry); return new DaoRegistryResourceSupportedSvc(theDaoRegistry);
@@ -138,8 +131,12 @@ public class StarterJpaConfig {
@Primary @Primary
@Bean @Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource myDataSource, ConfigurableListableBeanFactory myConfigurableListableBeanFactory, FhirContext theFhirContext) { public LocalContainerEntityManagerFactoryBean entityManagerFactory(
LocalContainerEntityManagerFactoryBean retVal = HapiEntityManagerFactoryUtil.newEntityManagerFactory(myConfigurableListableBeanFactory, theFhirContext); DataSource myDataSource,
ConfigurableListableBeanFactory myConfigurableListableBeanFactory,
FhirContext theFhirContext) {
LocalContainerEntityManagerFactoryBean retVal =
HapiEntityManagerFactoryUtil.newEntityManagerFactory(myConfigurableListableBeanFactory, theFhirContext);
retVal.setPersistenceUnitName("HAPI_PU"); retVal.setPersistenceUnitName("HAPI_PU");
try { try {
@@ -147,7 +144,8 @@ public class StarterJpaConfig {
} catch (Exception e) { } catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e); throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
} }
retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment, myConfigurableListableBeanFactory)); retVal.setJpaProperties(
EnvironmentHelper.getHibernateProperties(configurableEnvironment, myConfigurableListableBeanFactory));
return retVal; return retVal;
} }
@@ -164,10 +162,10 @@ public class StarterJpaConfig {
return new HSearchSortHelperImpl(mySearchParamRegistry); return new HSearchSortHelperImpl(mySearchParamRegistry);
} }
@Bean @Bean
@ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true") @ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true")
public RepositoryValidatingInterceptor repositoryValidatingInterceptor(IRepositoryValidationInterceptorFactory factory) { public RepositoryValidatingInterceptor repositoryValidatingInterceptor(
IRepositoryValidationInterceptorFactory factory) {
return factory.buildUsingStoredStructureDefinitions(); return factory.buildUsingStoredStructureDefinitions();
} }
@@ -189,8 +187,11 @@ public class StarterJpaConfig {
@Bean("packageInstaller") @Bean("packageInstaller")
@Primary @Primary
@Conditional(OnImplementationGuidesPresent.class) @Conditional(OnImplementationGuidesPresent.class)
public IPackageInstallerSvc packageInstaller(AppProperties appProperties, JobDefinition<ReindexJobParameters> reindexJobParametersJobDefinition, JobDefinitionRegistry jobDefinitionRegistry, IPackageInstallerSvc packageInstallerSvc) public IPackageInstallerSvc packageInstaller(
{ AppProperties appProperties,
JobDefinition<ReindexJobParameters> reindexJobParametersJobDefinition,
JobDefinitionRegistry jobDefinitionRegistry,
IPackageInstallerSvc packageInstallerSvc) {
jobDefinitionRegistry.addJobDefinitionIfNotRegistered(reindexJobParametersJobDefinition); jobDefinitionRegistry.addJobDefinitionIfNotRegistered(reindexJobParametersJobDefinition);
if (appProperties.getImplementationGuides() != null) { if (appProperties.getImplementationGuides() != null) {
@@ -199,7 +200,8 @@ public class StarterJpaConfig {
PackageInstallationSpec packageInstallationSpec = guidesEntry.getValue(); PackageInstallationSpec packageInstallationSpec = guidesEntry.getValue();
if (appProperties.getInstall_transitive_ig_dependencies()) { if (appProperties.getInstall_transitive_ig_dependencies()) {
packageInstallationSpec.addDependencyExclude("hl7.fhir.r2.core") packageInstallationSpec
.addDependencyExclude("hl7.fhir.r2.core")
.addDependencyExclude("hl7.fhir.r3.core") .addDependencyExclude("hl7.fhir.r3.core")
.addDependencyExclude("hl7.fhir.r4.core") .addDependencyExclude("hl7.fhir.r4.core")
.addDependencyExclude("hl7.fhir.r5.core"); .addDependencyExclude("hl7.fhir.r5.core");
@@ -238,11 +240,40 @@ public class StarterJpaConfig {
// Create the interceptor and register it // Create the interceptor and register it
return new CorsInterceptor(config); return new CorsInterceptor(config);
} }
@Bean @Bean
public RestfulServer restfulServer(IFhirSystemDao<?, ?> fhirSystemDao, AppProperties appProperties, DaoRegistry daoRegistry, Optional<MdmProviderLoader> mdmProviderProvider, IJpaSystemProvider jpaSystemProvider, ResourceProviderFactory resourceProviderFactory, JpaStorageSettings jpaStorageSettings, ISearchParamRegistry searchParamRegistry, IValidationSupport theValidationSupport, DatabaseBackedPagingProvider databaseBackedPagingProvider, LoggingInterceptor loggingInterceptor, Optional<TerminologyUploaderProvider> terminologyUploaderProvider, Optional<SubscriptionTriggeringProvider> subscriptionTriggeringProvider, Optional<CorsInterceptor> corsInterceptor, IInterceptorBroadcaster interceptorBroadcaster, Optional<BinaryAccessProvider> binaryAccessProvider, BinaryStorageInterceptor binaryStorageInterceptor, IValidatorModule validatorModule, Optional<GraphQLProvider> graphQLProvider, BulkDataExportProvider bulkDataExportProvider, BulkDataImportProvider bulkDataImportProvider, ValueSetOperationProvider theValueSetOperationProvider, ReindexProvider reindexProvider, PartitionManagementProvider partitionManagementProvider, Optional<RepositoryValidatingInterceptor> repositoryValidatingInterceptor, IPackageInstallerSvc packageInstallerSvc, ThreadSafeResourceDeleterSvc theThreadSafeResourceDeleterSvc, ApplicationContext appContext, Optional<IpsOperationProvider> theIpsOperationProvider, Optional<IImplementationGuideOperationProvider> implementationGuideOperationProvider) { public RestfulServer restfulServer(
IFhirSystemDao<?, ?> fhirSystemDao,
AppProperties appProperties,
DaoRegistry daoRegistry,
Optional<MdmProviderLoader> mdmProviderProvider,
IJpaSystemProvider jpaSystemProvider,
ResourceProviderFactory resourceProviderFactory,
JpaStorageSettings jpaStorageSettings,
ISearchParamRegistry searchParamRegistry,
IValidationSupport theValidationSupport,
DatabaseBackedPagingProvider databaseBackedPagingProvider,
LoggingInterceptor loggingInterceptor,
Optional<TerminologyUploaderProvider> terminologyUploaderProvider,
Optional<SubscriptionTriggeringProvider> subscriptionTriggeringProvider,
Optional<CorsInterceptor> corsInterceptor,
IInterceptorBroadcaster interceptorBroadcaster,
Optional<BinaryAccessProvider> binaryAccessProvider,
BinaryStorageInterceptor binaryStorageInterceptor,
IValidatorModule validatorModule,
Optional<GraphQLProvider> graphQLProvider,
BulkDataExportProvider bulkDataExportProvider,
BulkDataImportProvider bulkDataImportProvider,
ValueSetOperationProvider theValueSetOperationProvider,
ReindexProvider reindexProvider,
PartitionManagementProvider partitionManagementProvider,
Optional<RepositoryValidatingInterceptor> repositoryValidatingInterceptor,
IPackageInstallerSvc packageInstallerSvc,
ThreadSafeResourceDeleterSvc theThreadSafeResourceDeleterSvc,
ApplicationContext appContext,
Optional<IpsOperationProvider> theIpsOperationProvider,
Optional<IImplementationGuideOperationProvider> implementationGuideOperationProvider) {
RestfulServer fhirServer = new RestfulServer(fhirSystemDao.getContext()); RestfulServer fhirServer = new RestfulServer(fhirSystemDao.getContext());
List<String> supportedResourceTypes = appProperties.getSupported_resource_types(); List<String> supportedResourceTypes = appProperties.getSupported_resource_types();
@@ -264,7 +295,8 @@ public class StarterJpaConfig {
fhirServer.registerProviders(resourceProviderFactory.createProviders()); fhirServer.registerProviders(resourceProviderFactory.createProviders());
fhirServer.registerProvider(jpaSystemProvider); fhirServer.registerProvider(jpaSystemProvider);
fhirServer.setServerConformanceProvider(calculateConformanceProvider(fhirSystemDao, fhirServer, jpaStorageSettings, searchParamRegistry, theValidationSupport)); fhirServer.setServerConformanceProvider(calculateConformanceProvider(
fhirSystemDao, fhirServer, jpaStorageSettings, searchParamRegistry, theValidationSupport));
/* /*
* ETag Support * ETag Support
@@ -272,7 +304,6 @@ public class StarterJpaConfig {
if (!appProperties.getEtag_support_enabled()) fhirServer.setETagSupport(ETagSupportEnum.DISABLED); if (!appProperties.getEtag_support_enabled()) fhirServer.setETagSupport(ETagSupportEnum.DISABLED);
/* /*
* Default to JSON and pretty printing * Default to JSON and pretty printing
*/ */
@@ -318,7 +349,8 @@ public class StarterJpaConfig {
fhirServer.setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); fhirServer.setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress));
} else if (appProperties.getUse_apache_address_strategy()) { } else if (appProperties.getUse_apache_address_strategy()) {
boolean useHttps = appProperties.getUse_apache_address_strategy_https(); boolean useHttps = appProperties.getUse_apache_address_strategy_https();
fhirServer.setServerAddressStrategy(useHttps ? ApacheProxyAddressStrategy.forHttps() : ApacheProxyAddressStrategy.forHttp()); fhirServer.setServerAddressStrategy(
useHttps ? ApacheProxyAddressStrategy.forHttps() : ApacheProxyAddressStrategy.forHttp());
} else { } else {
fhirServer.setServerAddressStrategy(new IncomingRequestAddressStrategy()); fhirServer.setServerAddressStrategy(new IncomingRequestAddressStrategy());
} }
@@ -330,7 +362,11 @@ public class StarterJpaConfig {
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
* with this feature. * with this feature.
*/ */
if (fhirSystemDao.getContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { // <-- ENABLED RIGHT NOW if (fhirSystemDao
.getContext()
.getVersion()
.getVersion()
.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { // <-- ENABLED RIGHT NOW
fhirServer.registerProvider(terminologyUploaderProvider.get()); fhirServer.registerProvider(terminologyUploaderProvider.get());
} }
@@ -348,7 +384,8 @@ public class StarterJpaConfig {
} }
if (appProperties.getAllow_cascading_deletes()) { if (appProperties.getAllow_cascading_deletes()) {
CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(fhirSystemDao.getContext(), daoRegistry, interceptorBroadcaster, theThreadSafeResourceDeleterSvc); CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(
fhirSystemDao.getContext(), daoRegistry, interceptorBroadcaster, theThreadSafeResourceDeleterSvc);
fhirServer.registerInterceptor(cascadingDeleteInterceptor); fhirServer.registerInterceptor(cascadingDeleteInterceptor);
} }
@@ -391,16 +428,15 @@ public class StarterJpaConfig {
fhirServer.registerProvider(bulkDataExportProvider); fhirServer.registerProvider(bulkDataExportProvider);
} }
//Bulk Import // Bulk Import
if (appProperties.getBulk_import_enabled()) { if (appProperties.getBulk_import_enabled()) {
fhirServer.registerProvider(bulkDataImportProvider); fhirServer.registerProvider(bulkDataImportProvider);
} }
// valueSet Operations i.e $expand // valueSet Operations i.e $expand
fhirServer.registerProvider(theValueSetOperationProvider); fhirServer.registerProvider(theValueSetOperationProvider);
//reindex Provider $reindex // reindex Provider $reindex
fhirServer.registerProvider(reindexProvider); fhirServer.registerProvider(reindexProvider);
// Partitioning // Partitioning
@@ -414,8 +450,7 @@ public class StarterJpaConfig {
// register custom interceptors // register custom interceptors
registerCustomInterceptors(fhirServer, appContext, appProperties.getCustomInterceptorClasses()); registerCustomInterceptors(fhirServer, appContext, appProperties.getCustomInterceptorClasses());
// register the IPS Provider
//register the IPS Provider
if (!theIpsOperationProvider.isEmpty()) { if (!theIpsOperationProvider.isEmpty()) {
fhirServer.registerProvider(theIpsOperationProvider.get()); fhirServer.registerProvider(theIpsOperationProvider.get());
} }
@@ -426,8 +461,9 @@ public class StarterJpaConfig {
/** /**
* check the properties for custom interceptor classes and registers them. * check the properties for custom interceptor classes and registers them.
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"unchecked", "rawtypes"})
private void registerCustomInterceptors(RestfulServer fhirServer, ApplicationContext theAppContext, List<String> customInterceptorClasses) { private void registerCustomInterceptors(
RestfulServer fhirServer, ApplicationContext theAppContext, List<String> customInterceptorClasses) {
if (customInterceptorClasses == null) { if (customInterceptorClasses == null) {
return; return;
@@ -461,30 +497,40 @@ public class StarterJpaConfig {
} }
} }
public static IServerConformanceProvider<?> calculateConformanceProvider(IFhirSystemDao fhirSystemDao, RestfulServer fhirServer, JpaStorageSettings jpaStorageSettings, ISearchParamRegistry searchParamRegistry, IValidationSupport theValidationSupport) { public static IServerConformanceProvider<?> calculateConformanceProvider(
IFhirSystemDao fhirSystemDao,
RestfulServer fhirServer,
JpaStorageSettings jpaStorageSettings,
ISearchParamRegistry searchParamRegistry,
IValidationSupport theValidationSupport) {
FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion(); FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion();
if (fhirVersion == FhirVersionEnum.DSTU2) { if (fhirVersion == FhirVersionEnum.DSTU2) {
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(fhirServer, fhirSystemDao, jpaStorageSettings); JpaConformanceProviderDstu2 confProvider =
new JpaConformanceProviderDstu2(fhirServer, fhirSystemDao, jpaStorageSettings);
confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server");
return confProvider; return confProvider;
} else if (fhirVersion == FhirVersionEnum.DSTU3) { } else if (fhirVersion == FhirVersionEnum.DSTU3) {
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry); JpaConformanceProviderDstu3 confProvider =
new JpaConformanceProviderDstu3(fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry);
confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server");
return confProvider; return confProvider;
} else if (fhirVersion == FhirVersionEnum.R4) { } else if (fhirVersion == FhirVersionEnum.R4) {
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport); JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(
fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport);
confProvider.setImplementationDescription("HAPI FHIR R4 Server"); confProvider.setImplementationDescription("HAPI FHIR R4 Server");
return confProvider; return confProvider;
} else if (fhirVersion == FhirVersionEnum.R4B) { } else if (fhirVersion == FhirVersionEnum.R4B) {
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport); JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(
fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport);
confProvider.setImplementationDescription("HAPI FHIR R4B Server"); confProvider.setImplementationDescription("HAPI FHIR R4B Server");
return confProvider; return confProvider;
} else if (fhirVersion == FhirVersionEnum.R5) { } else if (fhirVersion == FhirVersionEnum.R5) {
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport); JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(
fhirServer, fhirSystemDao, jpaStorageSettings, searchParamRegistry, theValidationSupport);
confProvider.setImplementationDescription("HAPI FHIR R5 Server"); confProvider.setImplementationDescription("HAPI FHIR R5 Server");
return confProvider; return confProvider;
} else { } else {
@@ -492,4 +538,3 @@ public class StarterJpaConfig {
} }
} }
} }

View File

@@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
public interface IRepositoryValidationInterceptorFactory { public interface IRepositoryValidationInterceptorFactory {
String ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR = "enable_repository_validating_interceptor"; String ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR = "enable_repository_validating_interceptor";
RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions(); RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions();
RepositoryValidatingInterceptor build(); RepositoryValidatingInterceptor build();

View File

@@ -37,24 +37,28 @@ public class RepositoryValidationInterceptorFactoryDstu3 implements IRepositoryV
private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder;
private final IFhirResourceDao structureDefinitionResourceProvider; private final IFhirResourceDao structureDefinitionResourceProvider;
public RepositoryValidationInterceptorFactoryDstu3(RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { public RepositoryValidationInterceptorFactoryDstu3(
RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) {
this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder;
this.fhirContext = daoRegistry.getSystemDao().getContext(); this.fhirContext = daoRegistry.getSystemDao().getContext();
structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition");
} }
public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() {
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource"))); IBundleProvider results = structureDefinitionResourceProvider.search(
Map<String, List<StructureDefinition>> structureDefinitions = results.getResources(0, results.size()) new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource")));
.stream() Map<String, List<StructureDefinition>> structureDefinitions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefinitions.forEach((key, value) -> { structureDefinitions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder.forResourcesOfType(key).requireAtLeastOneProfileOf(urls).and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType(key)
.requireAtLeastOneProfileOf(urls)
.and()
.requireValidationToDeclaredProfiles();
}); });
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
@@ -65,11 +69,14 @@ public class RepositoryValidationInterceptorFactoryDstu3 implements IRepositoryV
// Customize the ruleBuilder here to have the rules you want! We will give a simple example // Customize the ruleBuilder here to have the rules you want! We will give a simple example
// of enabling validation for all Patient resources // of enabling validation for all Patient resources
repositoryValidatingRuleBuilder.forResourcesOfType("Patient").requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient").and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType("Patient")
.requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient")
.and()
.requireValidationToDeclaredProfiles();
// Do not customize below this line // Do not customize below this line
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
return new RepositoryValidatingInterceptor(fhirContext, rules); return new RepositoryValidatingInterceptor(fhirContext, rules);
} }
} }

View File

@@ -37,25 +37,29 @@ public class RepositoryValidationInterceptorFactoryR4 implements IRepositoryVali
private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder;
private final IFhirResourceDao structureDefinitionResourceProvider; private final IFhirResourceDao structureDefinitionResourceProvider;
public RepositoryValidationInterceptorFactoryR4(RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { public RepositoryValidationInterceptorFactoryR4(
RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) {
this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder;
this.fhirContext = daoRegistry.getSystemDao().getContext(); this.fhirContext = daoRegistry.getSystemDao().getContext();
structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition");
} }
@Override @Override
public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() {
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource"))); IBundleProvider results = structureDefinitionResourceProvider.search(
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()) new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource")));
.stream() Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefintions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder.forResourcesOfType(key).requireAtLeastOneProfileOf(urls).and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType(key)
.requireAtLeastOneProfileOf(urls)
.and()
.requireValidationToDeclaredProfiles();
}); });
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
@@ -67,11 +71,14 @@ public class RepositoryValidationInterceptorFactoryR4 implements IRepositoryVali
// Customize the ruleBuilder here to have the rules you want! We will give a simple example // Customize the ruleBuilder here to have the rules you want! We will give a simple example
// of enabling validation for all Patient resources // of enabling validation for all Patient resources
repositoryValidatingRuleBuilder.forResourcesOfType("Patient").requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient").and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType("Patient")
.requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient")
.and()
.requireValidationToDeclaredProfiles();
// Do not customize below this line // Do not customize below this line
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
return new RepositoryValidatingInterceptor(fhirContext, rules); return new RepositoryValidatingInterceptor(fhirContext, rules);
} }
} }

View File

@@ -37,25 +37,29 @@ public class RepositoryValidationInterceptorFactoryR4B implements IRepositoryVal
private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder;
private final IFhirResourceDao structureDefinitionResourceProvider; private final IFhirResourceDao structureDefinitionResourceProvider;
public RepositoryValidationInterceptorFactoryR4B(RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { public RepositoryValidationInterceptorFactoryR4B(
RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) {
this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder;
this.fhirContext = daoRegistry.getSystemDao().getContext(); this.fhirContext = daoRegistry.getSystemDao().getContext();
structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition");
} }
@Override @Override
public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() {
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource"))); IBundleProvider results = structureDefinitionResourceProvider.search(
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()) new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource")));
.stream() Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefintions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder.forResourcesOfType(key).requireAtLeastOneProfileOf(urls).and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType(key)
.requireAtLeastOneProfileOf(urls)
.and()
.requireValidationToDeclaredProfiles();
}); });
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
@@ -67,11 +71,14 @@ public class RepositoryValidationInterceptorFactoryR4B implements IRepositoryVal
// Customize the ruleBuilder here to have the rules you want! We will give a simple example // Customize the ruleBuilder here to have the rules you want! We will give a simple example
// of enabling validation for all Patient resources // of enabling validation for all Patient resources
repositoryValidatingRuleBuilder.forResourcesOfType("Patient").requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient").and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType("Patient")
.requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient")
.and()
.requireValidationToDeclaredProfiles();
// Do not customize below this line // Do not customize below this line
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
return new RepositoryValidatingInterceptor(fhirContext, rules); return new RepositoryValidatingInterceptor(fhirContext, rules);
} }
} }

View File

@@ -37,24 +37,28 @@ public class RepositoryValidationInterceptorFactoryR5 implements IRepositoryVali
private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder;
private final IFhirResourceDao structureDefinitionResourceProvider; private final IFhirResourceDao structureDefinitionResourceProvider;
public RepositoryValidationInterceptorFactoryR5(RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { public RepositoryValidationInterceptorFactoryR5(
RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) {
this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder;
this.fhirContext = daoRegistry.getSystemDao().getContext(); this.fhirContext = daoRegistry.getSystemDao().getContext();
structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition");
} }
public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() {
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource"))); IBundleProvider results = structureDefinitionResourceProvider.search(
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()) new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource")));
.stream() Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefintions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder.forResourcesOfType(key).requireAtLeastOneProfileOf(urls).and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType(key)
.requireAtLeastOneProfileOf(urls)
.and()
.requireValidationToDeclaredProfiles();
}); });
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
@@ -65,11 +69,14 @@ public class RepositoryValidationInterceptorFactoryR5 implements IRepositoryVali
// Customize the ruleBuilder here to have the rules you want! We will give a simple example // Customize the ruleBuilder here to have the rules you want! We will give a simple example
// of enabling validation for all Patient resources // of enabling validation for all Patient resources
repositoryValidatingRuleBuilder.forResourcesOfType("Patient").requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient").and().requireValidationToDeclaredProfiles(); repositoryValidatingRuleBuilder
.forResourcesOfType("Patient")
.requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient")
.and()
.requireValidationToDeclaredProfiles();
// Do not customize below this line // Do not customize below this line
List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build(); List<IRepositoryValidatingRule> rules = repositoryValidatingRuleBuilder.build();
return new RepositoryValidatingInterceptor(fhirContext, rules); return new RepositoryValidatingInterceptor(fhirContext, rules);
} }
} }

View File

@@ -6,9 +6,9 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
public class CrConfigCondition implements Condition { public class CrConfigCondition implements Condition {
@Override @Override
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.cr_enabled"); String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.cr.enabled");
return Boolean.parseBoolean(property); return Boolean.parseBoolean(property);
} }
} }

View File

@@ -0,0 +1,264 @@
package ca.uhn.fhir.jpa.starter.cr;
import org.cqframework.cql.cql2elm.CqlCompilerException;
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.LibraryBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hapi.fhir.cr")
public class CrProperties {
private Boolean enabled;
// cql settings
private Boolean cql_use_embedded_libraries = true;
private Boolean cql_runtime_debug_logging_enabled = false;
private Boolean cql_runtime_enable_validation = false;
private Boolean cql_runtime_enable_expression_caching = false;
private Boolean cql_compiler_validate_units = true;
private Boolean cql_compiler_verify_only = false;
private String cql_compiler_compatibility_level = "1.5";
private CqlCompilerException.ErrorSeverity cql_compiler_error_level = CqlCompilerException.ErrorSeverity.Info;
private LibraryBuilder.SignatureLevel cql_compiler_signature_level = LibraryBuilder.SignatureLevel.All;
private Boolean cql_compiler_analyze_data_requirements = false;
private Boolean cql_compiler_collapse_data_requirements = false;
private CqlTranslator.Format cql_compiler_translator_format = CqlTranslator.Format.JSON;
private Boolean cql_compiler_enable_date_range_optimization = false;
private Boolean cql_compiler_enable_annotations = false;
private Boolean cql_compiler_enable_locators = false;
private Boolean cql_compiler_enable_results_type = false;
private Boolean cql_compiler_enable_detailed_errors = false;
private Boolean cql_compiler_disable_list_traversal = false;
private Boolean cql_compiler_disable_list_demotion = false;
private Boolean cql_compiler_disable_list_promotion = false;
private Boolean cql_compiler_enable_interval_demotion = false;
private Boolean cql_compiler_enable_interval_promotion = false;
private Boolean cql_compiler_disable_method_invocation = false;
private Boolean cql_compiler_require_from_keyword = false;
private Boolean cql_compiler_disable_default_model_info_load = false;
// Care-gaps Settings
private String caregaps_reporter = "default";
private String caregaps_section_author = "default";
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public boolean isCqlUseEmbeddedLibraries() {
return cql_use_embedded_libraries;
}
public void setCqlUseEmbeddedLibraries(boolean cql_use_embedded_libraries) {
this.cql_use_embedded_libraries = cql_use_embedded_libraries;
}
public boolean isCqlRuntimeDebugLoggingEnabled() {
return cql_runtime_debug_logging_enabled;
}
public void setCqlRuntimeDebugLoggingEnabled(boolean cqlRuntimeDebugLoggingEnabled) {
this.cql_runtime_debug_logging_enabled = cqlRuntimeDebugLoggingEnabled;
}
public boolean isCqlCompilerValidateUnits() {
return cql_compiler_validate_units;
}
public void setCqlCompilerValidateUnits(boolean cqlCompilerValidateUnits) {
this.cql_compiler_validate_units = cqlCompilerValidateUnits;
}
public boolean isCqlCompilerVerifyOnly() {
return cql_compiler_verify_only;
}
public void setCqlCompilerVerifyOnly(boolean cqlCompilerVerifyOnly) {
this.cql_compiler_verify_only = cqlCompilerVerifyOnly;
}
public String getCqlCompilerCompatibilityLevel() {
return cql_compiler_compatibility_level;
}
public void setCqlCompilerCompatibilityLevel(String cqlCompilerCompatibilityLevel) {
this.cql_compiler_compatibility_level = cqlCompilerCompatibilityLevel;
}
public CqlCompilerException.ErrorSeverity getCqlCompilerErrorSeverityLevel() {
return cql_compiler_error_level;
}
public void setCqlCompilerErrorSeverityLevel(CqlCompilerException.ErrorSeverity cqlCompilerErrorSeverityLevel) {
this.cql_compiler_error_level = cqlCompilerErrorSeverityLevel;
}
public LibraryBuilder.SignatureLevel getCqlCompilerSignatureLevel() {
return cql_compiler_signature_level;
}
public void setCqlCompilerSignatureLevel(LibraryBuilder.SignatureLevel cqlCompilerSignatureLevel) {
this.cql_compiler_signature_level = cqlCompilerSignatureLevel;
}
public boolean isCqlCompilerAnalyzeDataRequirements() {
return cql_compiler_analyze_data_requirements;
}
public void setCqlCompilerAnalyzeDataRequirements(boolean cqlCompilerAnalyzeDataRequirements) {
this.cql_compiler_analyze_data_requirements = cqlCompilerAnalyzeDataRequirements;
}
public boolean isCqlCompilerCollapseDataRequirements() {
return cql_compiler_collapse_data_requirements;
}
public void setCqlCompilerCollapseDataRequirements(boolean cqlCompilerCollapseDataRequirements) {
this.cql_compiler_collapse_data_requirements = cqlCompilerCollapseDataRequirements;
}
public boolean isEnableDateRangeOptimization() {
return cql_compiler_enable_date_range_optimization;
}
public void setEnableDateRangeOptimization(boolean enableDateRangeOptimization) {
this.cql_compiler_enable_date_range_optimization = enableDateRangeOptimization;
}
public boolean isEnableAnnotations() {
return cql_compiler_enable_annotations;
}
public void setEnableAnnotations(boolean enableAnnotations) {
this.cql_compiler_enable_annotations = enableAnnotations;
}
public boolean isEnableLocators() {
return cql_compiler_enable_locators;
}
public void setEnableLocators(boolean enableLocators) {
this.cql_compiler_enable_locators = enableLocators;
}
public boolean isEnableResultsType() {
return cql_compiler_enable_results_type;
}
public void setEnableResultsType(boolean enableResultsType) {
this.cql_compiler_enable_results_type = enableResultsType;
}
public boolean isEnableDetailedErrors() {
return cql_compiler_enable_detailed_errors;
}
public void setEnableDetailedErrors(boolean enableDetailedErrors) {
this.cql_compiler_enable_detailed_errors = enableDetailedErrors;
}
public boolean isDisableListTraversal() {
return cql_compiler_disable_list_traversal;
}
public void setDisableListTraversal(boolean disableListTraversal) {
this.cql_compiler_disable_list_traversal = disableListTraversal;
}
public boolean isDisableListDemotion() {
return cql_compiler_disable_list_demotion;
}
public void setDisableListDemotion(boolean disableListDemotion) {
this.cql_compiler_disable_list_demotion = disableListDemotion;
}
public boolean isDisableListPromotion() {
return cql_compiler_disable_list_promotion;
}
public void setDisableListPromotion(boolean disableListPromotion) {
this.cql_compiler_disable_list_promotion = disableListPromotion;
}
public boolean isEnableIntervalPromotion() {
return cql_compiler_enable_interval_promotion;
}
public void setEnableIntervalPromotion(boolean enableIntervalPromotion) {
this.cql_compiler_enable_interval_promotion = enableIntervalPromotion;
}
public boolean isEnableIntervalDemotion() {
return cql_compiler_enable_interval_demotion;
}
public void setEnableIntervalDemotion(boolean enableIntervalDemotion) {
this.cql_compiler_enable_interval_demotion = enableIntervalDemotion;
}
public boolean isDisableMethodInvocation() {
return cql_compiler_disable_method_invocation;
}
public void setDisableMethodInvocation(boolean disableMethodInvocation) {
this.cql_compiler_disable_method_invocation = disableMethodInvocation;
}
public boolean isRequireFromKeyword() {
return cql_compiler_require_from_keyword;
}
public void setRequireFromKeyword(boolean requireFromKeyword) {
this.cql_compiler_require_from_keyword = requireFromKeyword;
}
public boolean isDisableDefaultModelInfoLoad() {
return cql_compiler_disable_default_model_info_load;
}
public void setDisableDefaultModelInfoLoad(boolean disableDefaultModelInfoLoad) {
this.cql_compiler_disable_default_model_info_load = disableDefaultModelInfoLoad;
}
public boolean isCqlRuntimeEnableExpressionCaching() {
return cql_runtime_enable_expression_caching;
}
public void setCqlRuntimeEnableExpressionCaching(boolean cqlRuntimeEnableExpressionCaching) {
this.cql_runtime_enable_expression_caching = cqlRuntimeEnableExpressionCaching;
}
public boolean isCqlRuntimeEnableValidation() {
return cql_runtime_enable_validation;
}
public void setCqlRuntimeEnableValidation(boolean cqlRuntimeEnableValidation) {
this.cql_runtime_enable_validation = cqlRuntimeEnableValidation;
}
public CqlTranslator.Format getCqlTranslatorFormat() {
return cql_compiler_translator_format;
}
public void setCqlTranslatorFormat(CqlTranslator.Format cqlTranslatorFormat) {
this.cql_compiler_translator_format = cqlTranslatorFormat;
}
public String getCareGapsReporter() {
return caregaps_reporter;
}
public String getCareGapsSectionAuthor() {
return caregaps_section_author;
}
public void setCareGapsSectionAuthor(String theCareGapsSectionAuthor) {
this.caregaps_section_author = theCareGapsSectionAuthor;
}
public void setCareGapsReporter(String theCareGapsReporter) {
this.caregaps_reporter = theCareGapsReporter;
}
}

View File

@@ -0,0 +1,47 @@
package ca.uhn.fhir.jpa.starter.cr;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.provider.IResourceProviderFactoryObserver;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import java.util.function.Supplier;
public class PostInitProviderRegisterer {
public PostInitProviderRegisterer(RestfulServer restfulServer, ResourceProviderFactory resourceProviderFactory) {
resourceProviderFactory.attach(new Observer(restfulServer));
}
private class Observer implements IResourceProviderFactoryObserver {
private RestfulServer restfulServer;
public Observer(RestfulServer restfulServer) {
this.restfulServer = restfulServer;
}
public void update(Supplier<Object> theSupplier) {
if (theSupplier == null) {
return;
}
var provider = theSupplier.get();
if (provider == null) {
return;
}
this.restfulServer.registerProvider(provider);
}
public void remove(Supplier<Object> theSupplier) {
if (theSupplier == null) {
return;
}
var provider = theSupplier.get();
if (provider == null) {
return;
}
this.restfulServer.unregisterProvider(provider);
}
}
}

View File

@@ -1,13 +1,195 @@
package ca.uhn.fhir.jpa.starter.cr; package ca.uhn.fhir.jpa.starter.cr;
import ca.uhn.fhir.cr.common.CodeCacheResourceChangeListener;
import ca.uhn.fhir.cr.common.ElmCacheResourceChangeListener;
import ca.uhn.fhir.cr.config.dstu3.ApplyOperationConfig;
import ca.uhn.fhir.cr.config.dstu3.CrDstu3Config; import ca.uhn.fhir.cr.config.dstu3.CrDstu3Config;
import ca.uhn.fhir.cr.config.dstu3.ExtractOperationConfig;
import ca.uhn.fhir.cr.config.dstu3.PackageOperationConfig;
import ca.uhn.fhir.cr.config.dstu3.PopulateOperationConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryInterceptor;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition;
import org.springframework.context.annotation.Conditional; import ca.uhn.fhir.rest.server.RestfulServer;
import org.springframework.context.annotation.Configuration; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import org.springframework.context.annotation.Import; import org.cqframework.cql.cql2elm.CqlCompilerOptions;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.cql2elm.model.Model;
import org.hl7.cql.model.ModelIdentifier;
import org.hl7.elm.r1.VersionedIdentifier;
import org.opencds.cqf.cql.engine.execution.CqlEngine;
import org.opencds.cqf.cql.engine.runtime.Code;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.utility.ValidationProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.*;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Configuration @Configuration
@Conditional({OnDSTU3Condition.class, CrConfigCondition.class}) @Conditional({OnDSTU3Condition.class, CrConfigCondition.class})
@Import({CrDstu3Config.class}) @Import({
// BaseCrConfig.class,
CrDstu3Config.class,
ApplyOperationConfig.class,
ExtractOperationConfig.class,
PackageOperationConfig.class,
PopulateOperationConfig.class
})
public class StarterCrDstu3Config { public class StarterCrDstu3Config {
private static final Logger ourLogger = LoggerFactory.getLogger(StarterCrDstu3Config.class);
@Bean
MeasureEvaluationOptions measureEvaluationOptions(
EvaluationSettings theEvaluationSettings, Map<String, ValidationProfile> theValidationProfiles) {
MeasureEvaluationOptions measureEvalOptions = new MeasureEvaluationOptions();
measureEvalOptions.setEvaluationSettings(theEvaluationSettings);
if (measureEvalOptions.isValidationEnabled()) {
measureEvalOptions.setValidationProfiles(theValidationProfiles);
}
return measureEvalOptions;
}
@Bean
public EvaluationSettings evaluationSettings(
CrProperties theCrProperties,
Map<VersionedIdentifier, CompiledLibrary> theGlobalLibraryCache,
Map<ModelIdentifier, Model> theGlobalModelCache,
Map<String, List<Code>> theGlobalValueSetCache) {
var evaluationSettings = EvaluationSettings.getDefault();
var cqlOptions = evaluationSettings.getCqlOptions();
var cqlEngineOptions = cqlOptions.getCqlEngineOptions();
Set<CqlEngine.Options> options = EnumSet.noneOf(CqlEngine.Options.class);
if (theCrProperties.isCqlRuntimeEnableExpressionCaching()) {
options.add(CqlEngine.Options.EnableExpressionCaching);
}
if (theCrProperties.isCqlRuntimeEnableValidation()) {
options.add(CqlEngine.Options.EnableValidation);
}
cqlEngineOptions.setOptions(options);
cqlOptions.setCqlEngineOptions(cqlEngineOptions);
var cqlCompilerOptions = new CqlCompilerOptions();
if (theCrProperties.isEnableDateRangeOptimization()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableDateRangeOptimization);
}
if (theCrProperties.isEnableAnnotations()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableAnnotations);
}
if (theCrProperties.isEnableLocators()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableLocators);
}
if (theCrProperties.isEnableResultsType()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableResultTypes);
}
cqlCompilerOptions.setVerifyOnly(theCrProperties.isCqlCompilerVerifyOnly());
if (theCrProperties.isEnableDetailedErrors()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableDetailedErrors);
}
cqlCompilerOptions.setErrorLevel(theCrProperties.getCqlCompilerErrorSeverityLevel());
if (theCrProperties.isDisableListTraversal()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListTraversal);
}
if (theCrProperties.isDisableListDemotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListDemotion);
}
if (theCrProperties.isDisableListPromotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListPromotion);
}
if (theCrProperties.isEnableIntervalDemotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableIntervalDemotion);
}
if (theCrProperties.isEnableIntervalPromotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableIntervalPromotion);
}
if (theCrProperties.isDisableMethodInvocation()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableMethodInvocation);
}
if (theCrProperties.isRequireFromKeyword()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.RequireFromKeyword);
}
cqlCompilerOptions.setValidateUnits(theCrProperties.isCqlCompilerValidateUnits());
if (theCrProperties.isDisableDefaultModelInfoLoad()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableDefaultModelInfoLoad);
}
cqlCompilerOptions.setSignatureLevel(theCrProperties.getCqlCompilerSignatureLevel());
cqlCompilerOptions.setCompatibilityLevel(theCrProperties.getCqlCompilerCompatibilityLevel());
cqlCompilerOptions.setAnalyzeDataRequirements(theCrProperties.isCqlCompilerAnalyzeDataRequirements());
cqlCompilerOptions.setCollapseDataRequirements(theCrProperties.isCqlCompilerCollapseDataRequirements());
cqlOptions.setCqlCompilerOptions(cqlCompilerOptions);
evaluationSettings.setLibraryCache(theGlobalLibraryCache);
evaluationSettings.setModelCache(theGlobalModelCache);
evaluationSettings.setValueSetCache(theGlobalValueSetCache);
return evaluationSettings;
}
@Bean
public PostInitProviderRegisterer postInitProviderRegisterer(
RestfulServer theRestfulServer, ResourceProviderFactory theResourceProviderFactory) {
return new PostInitProviderRegisterer(theRestfulServer, theResourceProviderFactory);
}
@Bean
public CrProperties crProperties() {
return new CrProperties();
}
@Bean
public Map<VersionedIdentifier, CompiledLibrary> globalLibraryCache() {
return new ConcurrentHashMap<>();
}
@Bean
public Map<ModelIdentifier, Model> globalModelCache() {
return new ConcurrentHashMap<>();
}
@Bean
public Map<String, List<Code>> globalValueSetCache() {
return new ConcurrentHashMap<>();
}
@Bean
public ElmCacheResourceChangeListener elmCacheResourceChangeListener(
IResourceChangeListenerRegistry theResourceChangeListenerRegistry,
DaoRegistry theDaoRegistry,
EvaluationSettings theEvaluationSettings) {
ElmCacheResourceChangeListener listener =
new ElmCacheResourceChangeListener(theDaoRegistry, theEvaluationSettings.getLibraryCache());
theResourceChangeListenerRegistry.registerResourceResourceChangeListener(
"Library", SearchParameterMap.newSynchronous(), listener, 1000);
return listener;
}
@Bean
public CodeCacheResourceChangeListener codeCacheResourceChangeListener(
IResourceChangeListenerRegistry theResourceChangeListenerRegistry,
EvaluationSettings theEvaluationSettings,
DaoRegistry theDaoRegistry) {
CodeCacheResourceChangeListener listener =
new CodeCacheResourceChangeListener(theDaoRegistry, theEvaluationSettings.getValueSetCache());
// registry
theResourceChangeListenerRegistry.registerResourceResourceChangeListener(
"ValueSet", SearchParameterMap.newSynchronous(), listener, 1000);
return listener;
}
@Bean
public ResourceChangeListenerRegistryInterceptor resourceChangeListenerRegistryInterceptor() {
return new ResourceChangeListenerRegistryInterceptor();
}
} }

View File

@@ -1,11 +1,221 @@
package ca.uhn.fhir.jpa.starter.cr; package ca.uhn.fhir.jpa.starter.cr;
import ca.uhn.fhir.cr.common.CodeCacheResourceChangeListener;
import ca.uhn.fhir.cr.common.CqlThreadFactory;
import ca.uhn.fhir.cr.common.ElmCacheResourceChangeListener;
import ca.uhn.fhir.cr.config.r4.ApplyOperationConfig;
import ca.uhn.fhir.cr.config.r4.CrR4Config; import ca.uhn.fhir.cr.config.r4.CrR4Config;
import ca.uhn.fhir.cr.config.r4.ExtractOperationConfig;
import ca.uhn.fhir.cr.config.r4.PackageOperationConfig;
import ca.uhn.fhir.cr.config.r4.PopulateOperationConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryInterceptor;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import org.cqframework.cql.cql2elm.CqlCompilerOptions;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.cql2elm.model.Model;
import org.hl7.cql.model.ModelIdentifier;
import org.hl7.elm.r1.VersionedIdentifier;
import org.opencds.cqf.cql.engine.execution.CqlEngine;
import org.opencds.cqf.cql.engine.runtime.Code;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
import org.opencds.cqf.fhir.cr.measure.CareGapsProperties;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.utility.ValidationProfile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
@Conditional({OnR4Condition.class, CrConfigCondition.class}) @Conditional({OnR4Condition.class, CrConfigCondition.class})
@Import({CrR4Config.class}) @Import({
CrR4Config.class,
ApplyOperationConfig.class,
ExtractOperationConfig.class,
PackageOperationConfig.class,
PopulateOperationConfig.class
})
public class StarterCrR4Config { public class StarterCrR4Config {
private static final Logger ourLogger = LoggerFactory.getLogger(StarterCrR4Config.class);
@Primary
@Bean
public ExecutorService cqlExecutor() {
CqlThreadFactory factory = new CqlThreadFactory();
ExecutorService executor = Executors.newFixedThreadPool(2, factory);
executor = new DelegatingSecurityContextExecutorService(executor);
return executor;
}
@Bean
CareGapsProperties careGapsProperties(CrProperties theCrProperties) {
var careGapsProperties = new CareGapsProperties();
careGapsProperties.setThreadedCareGapsEnabled(false);
careGapsProperties.setCareGapsReporter(theCrProperties.getCareGapsReporter());
careGapsProperties.setCareGapsCompositionSectionAuthor(theCrProperties.getCareGapsSectionAuthor());
return careGapsProperties;
}
@Bean
MeasureEvaluationOptions measureEvaluationOptions(
EvaluationSettings theEvaluationSettings, Map<String, ValidationProfile> theValidationProfiles) {
MeasureEvaluationOptions measureEvalOptions = new MeasureEvaluationOptions();
measureEvalOptions.setEvaluationSettings(theEvaluationSettings);
if (measureEvalOptions.isValidationEnabled()) {
measureEvalOptions.setValidationProfiles(theValidationProfiles);
}
return measureEvalOptions;
}
@Bean
public EvaluationSettings evaluationSettings(
CrProperties theCrProperties,
Map<VersionedIdentifier, CompiledLibrary> theGlobalLibraryCache,
Map<ModelIdentifier, Model> theGlobalModelCache,
Map<String, List<Code>> theGlobalValueSetCache) {
var evaluationSettings = EvaluationSettings.getDefault();
var cqlOptions = evaluationSettings.getCqlOptions();
var cqlEngineOptions = cqlOptions.getCqlEngineOptions();
Set<CqlEngine.Options> options = EnumSet.noneOf(CqlEngine.Options.class);
if (theCrProperties.isCqlRuntimeEnableExpressionCaching()) {
options.add(CqlEngine.Options.EnableExpressionCaching);
}
if (theCrProperties.isCqlRuntimeEnableValidation()) {
options.add(CqlEngine.Options.EnableValidation);
}
cqlEngineOptions.setOptions(options);
cqlOptions.setCqlEngineOptions(cqlEngineOptions);
var cqlCompilerOptions = new CqlCompilerOptions();
if (theCrProperties.isEnableDateRangeOptimization()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableDateRangeOptimization);
}
if (theCrProperties.isEnableAnnotations()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableAnnotations);
}
if (theCrProperties.isEnableLocators()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableLocators);
}
if (theCrProperties.isEnableResultsType()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableResultTypes);
}
cqlCompilerOptions.setVerifyOnly(theCrProperties.isCqlCompilerVerifyOnly());
if (theCrProperties.isEnableDetailedErrors()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableDetailedErrors);
}
cqlCompilerOptions.setErrorLevel(theCrProperties.getCqlCompilerErrorSeverityLevel());
if (theCrProperties.isDisableListTraversal()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListTraversal);
}
if (theCrProperties.isDisableListDemotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListDemotion);
}
if (theCrProperties.isDisableListPromotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableListPromotion);
}
if (theCrProperties.isEnableIntervalDemotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableIntervalDemotion);
}
if (theCrProperties.isEnableIntervalPromotion()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.EnableIntervalPromotion);
}
if (theCrProperties.isDisableMethodInvocation()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableMethodInvocation);
}
if (theCrProperties.isRequireFromKeyword()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.RequireFromKeyword);
}
cqlCompilerOptions.setValidateUnits(theCrProperties.isCqlCompilerValidateUnits());
if (theCrProperties.isDisableDefaultModelInfoLoad()) {
cqlCompilerOptions.setOptions(CqlCompilerOptions.Options.DisableDefaultModelInfoLoad);
}
cqlCompilerOptions.setSignatureLevel(theCrProperties.getCqlCompilerSignatureLevel());
cqlCompilerOptions.setCompatibilityLevel(theCrProperties.getCqlCompilerCompatibilityLevel());
cqlCompilerOptions.setAnalyzeDataRequirements(theCrProperties.isCqlCompilerAnalyzeDataRequirements());
cqlCompilerOptions.setCollapseDataRequirements(theCrProperties.isCqlCompilerCollapseDataRequirements());
cqlOptions.setCqlCompilerOptions(cqlCompilerOptions);
evaluationSettings.setLibraryCache(theGlobalLibraryCache);
evaluationSettings.setModelCache(theGlobalModelCache);
evaluationSettings.setValueSetCache(theGlobalValueSetCache);
return evaluationSettings;
}
@Bean
public PostInitProviderRegisterer postInitProviderRegisterer(
RestfulServer theRestfulServer, ResourceProviderFactory theResourceProviderFactory) {
return new PostInitProviderRegisterer(theRestfulServer, theResourceProviderFactory);
}
@Bean
public CrProperties crProperties() {
return new CrProperties();
}
@Bean
public Map<VersionedIdentifier, CompiledLibrary> globalLibraryCache() {
return new ConcurrentHashMap<>();
}
@Bean
public Map<ModelIdentifier, Model> globalModelCache() {
return new ConcurrentHashMap<>();
}
@Bean
public Map<String, List<Code>> globalValueSetCache() {
return new ConcurrentHashMap<>();
}
@Bean
public ElmCacheResourceChangeListener elmCacheResourceChangeListener(
IResourceChangeListenerRegistry theResourceChangeListenerRegistry,
DaoRegistry theDaoRegistry,
EvaluationSettings theEvaluationSettings) {
ElmCacheResourceChangeListener listener =
new ElmCacheResourceChangeListener(theDaoRegistry, theEvaluationSettings.getLibraryCache());
theResourceChangeListenerRegistry.registerResourceResourceChangeListener(
"Library", SearchParameterMap.newSynchronous(), listener, 1000);
return listener;
}
@Bean
public CodeCacheResourceChangeListener codeCacheResourceChangeListener(
IResourceChangeListenerRegistry theResourceChangeListenerRegistry,
EvaluationSettings theEvaluationSettings,
DaoRegistry theDaoRegistry) {
CodeCacheResourceChangeListener listener =
new CodeCacheResourceChangeListener(theDaoRegistry, theEvaluationSettings.getValueSetCache());
// registry
theResourceChangeListenerRegistry.registerResourceResourceChangeListener(
"ValueSet", SearchParameterMap.newSynchronous(), listener, 1000);
return listener;
}
@Bean
public ResourceChangeListenerRegistryInterceptor resourceChangeListenerRegistryInterceptor() {
return new ResourceChangeListenerRegistryInterceptor();
}
} }

View File

@@ -9,10 +9,16 @@ import java.io.IOException;
public interface IImplementationGuideOperationProvider { public interface IImplementationGuideOperationProvider {
static PackageInstallationSpec toPackageInstallationSpec(byte[] npmPackageAsByteArray) throws IOException { static PackageInstallationSpec toPackageInstallationSpec(byte[] npmPackageAsByteArray) throws IOException {
NpmPackage npmPackage = NpmPackage.fromPackage(new ByteArrayInputStream(npmPackageAsByteArray)); NpmPackage npmPackage = NpmPackage.fromPackage(new ByteArrayInputStream(npmPackageAsByteArray));
return new PackageInstallationSpec().setName(npmPackage.name()).setPackageContents(npmPackageAsByteArray).setVersion(npmPackage.version()).setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL).setFetchDependencies(false); return new PackageInstallationSpec()
.setName(npmPackage.name())
.setPackageContents(npmPackageAsByteArray)
.setVersion(npmPackage.version())
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)
.setFetchDependencies(false);
} }
//The following declaration is the one that counts but cannot be used across different versions as stating Base64BinaryType would bind to a separate version // The following declaration is the one that counts but cannot be used across different versions as stating
//@Operation(name = "$install", typeName = "ImplementationGuide") // Base64BinaryType would bind to a separate version
//Parameters install(@OperationParam(name = "npmContent",min = 1, max = 1) Base64BinaryType implementationGuide); // @Operation(name = "$install", typeName = "ImplementationGuide")
// Parameters install(@OperationParam(name = "npmContent",min = 1, max = 1) Base64BinaryType implementationGuide);
} }

View File

@@ -22,14 +22,15 @@ public class ImplementationGuideR4OperationProvider implements IImplementationGu
} }
@Operation(name = "$install", typeName = "ImplementationGuide") @Operation(name = "$install", typeName = "ImplementationGuide")
public Parameters install(@OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) { public Parameters install(
@OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) {
try { try {
packageInstallerSvc.install(IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue())); packageInstallerSvc.install(
IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue()));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return new Parameters(); return new Parameters();
} }
} }

View File

@@ -22,15 +22,15 @@ public class ImplementationGuideR5OperationProvider {
} }
@Operation(name = "$install", typeName = "ImplementationGuide") @Operation(name = "$install", typeName = "ImplementationGuide")
public Parameters install(@OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) { public Parameters install(
@OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) {
try { try {
packageInstallerSvc.install(IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue())); packageInstallerSvc.install(
IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue()));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return new Parameters(); return new Parameters();
} }
} }

View File

@@ -7,8 +7,8 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
public class IpsConfigCondition implements Condition { public class IpsConfigCondition implements Condition {
@Override @Override
public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) {
String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ips_enabled"); String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ips_enabled");
return Boolean.parseBoolean(property); return Boolean.parseBoolean(property);
} }
} }

View File

@@ -1,34 +1,30 @@
package ca.uhn.fhir.jpa.starter.ips; package ca.uhn.fhir.jpa.starter.ips;
import org.springframework.context.annotation.Bean;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy; import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc; import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
import org.springframework.context.annotation.Conditional;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl; import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@Conditional(IpsConfigCondition.class) @Conditional(IpsConfigCondition.class)
public class StarterIpsConfig { public class StarterIpsConfig {
@Bean @Bean
IIpsGenerationStrategy ipsGenerationStrategy() IIpsGenerationStrategy ipsGenerationStrategy() {
{
return new DefaultIpsGenerationStrategy(); return new DefaultIpsGenerationStrategy();
} }
@Bean @Bean
public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc){ public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) {
return new IpsOperationProvider(theIpsGeneratorSvc); return new IpsOperationProvider(theIpsGeneratorSvc);
} }
@Bean @Bean
public IIpsGeneratorSvc ipsGeneratorSvcImpl(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) public IIpsGeneratorSvc ipsGeneratorSvcImpl(
{ FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry); return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
} }
} }

View File

@@ -24,10 +24,13 @@ import java.nio.charset.StandardCharsets;
public class MdmConfig { public class MdmConfig {
@Bean @Bean
IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties) throws IOException { IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties)
throws IOException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("mdm-rules.json"); Resource resource = resourceLoader.getResource("mdm-rules.json");
String json = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); String json = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8);
return new MdmSettings(theMdmRuleValidator).setEnabled(appProperties.getMdm_enabled()).setScriptText(json); return new MdmSettings(theMdmRuleValidator)
.setEnabled(appProperties.getMdm_enabled())
.setScriptText(json);
} }
} }

View File

@@ -5,9 +5,9 @@ import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
public class MdmConfigCondition implements Condition { public class MdmConfigCondition implements Condition {
@Override @Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String property = conditionContext.getEnvironment().getProperty("hapi.fhir.mdm_enabled"); String property = conditionContext.getEnvironment().getProperty("hapi.fhir.mdm_enabled");
return Boolean.parseBoolean(property); return Boolean.parseBoolean(property);
} }
} }

View File

@@ -31,8 +31,8 @@ import static java.util.Objects.requireNonNullElse;
public class EnvironmentHelper { public class EnvironmentHelper {
public static Properties getHibernateProperties(ConfigurableEnvironment environment, public static Properties getHibernateProperties(
ConfigurableListableBeanFactory myConfigurableListableBeanFactory) { ConfigurableEnvironment environment, ConfigurableListableBeanFactory myConfigurableListableBeanFactory) {
Properties properties = new Properties(); Properties properties = new Properties();
Map<String, Object> jpaProps = getPropertiesStartingWith(environment, "spring.jpa.properties"); Map<String, Object> jpaProps = getPropertiesStartingWith(environment, "spring.jpa.properties");
for (Map.Entry<String, Object> entry : jpaProps.entrySet()) { for (Map.Entry<String, Object> entry : jpaProps.entrySet()) {
@@ -40,18 +40,23 @@ public class EnvironmentHelper {
properties.put(strippedKey, entry.getValue().toString()); properties.put(strippedKey, entry.getValue().toString());
} }
//Spring Boot Autoconfiguration defaults // Spring Boot Autoconfiguration defaults
properties.putIfAbsent(AvailableSettings.SCANNER, "org.hibernate.boot.archive.scan.internal.DisabledScanner"); properties.putIfAbsent(AvailableSettings.SCANNER, "org.hibernate.boot.archive.scan.internal.DisabledScanner");
properties.putIfAbsent(AvailableSettings.IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.class.getName()); properties.putIfAbsent(
properties.putIfAbsent(AvailableSettings.PHYSICAL_NAMING_STRATEGY, CamelCaseToUnderscoresNamingStrategy.class.getName()); AvailableSettings.IMPLICIT_NAMING_STRATEGY, SpringImplicitNamingStrategy.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(
//properties.putIfAbsent(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory)); 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" // hapi-fhir-jpaserver-base "sensible defaults"
Map<String, Object> hapiJpaPropertyMap = new HapiFhirLocalContainerEntityManagerFactoryBean(myConfigurableListableBeanFactory).getJpaPropertyMap(); Map<String, Object> hapiJpaPropertyMap = new HapiFhirLocalContainerEntityManagerFactoryBean(
myConfigurableListableBeanFactory)
.getJpaPropertyMap();
hapiJpaPropertyMap.forEach(properties::putIfAbsent); hapiJpaPropertyMap.forEach(properties::putIfAbsent);
//hapi-fhir-jpaserver-starter defaults // hapi-fhir-jpaserver-starter defaults
properties.putIfAbsent(AvailableSettings.FORMAT_SQL, false); properties.putIfAbsent(AvailableSettings.FORMAT_SQL, false);
properties.putIfAbsent(AvailableSettings.SHOW_SQL, false); properties.putIfAbsent(AvailableSettings.SHOW_SQL, false);
properties.putIfAbsent(AvailableSettings.HBM2DDL_AUTO, "update"); properties.putIfAbsent(AvailableSettings.HBM2DDL_AUTO, "update");
@@ -61,43 +66,60 @@ public class EnvironmentHelper {
properties.putIfAbsent(AvailableSettings.USE_STRUCTURED_CACHE, false); properties.putIfAbsent(AvailableSettings.USE_STRUCTURED_CACHE, false);
properties.putIfAbsent(AvailableSettings.USE_MINIMAL_PUTS, false); properties.putIfAbsent(AvailableSettings.USE_MINIMAL_PUTS, false);
//Hibernate Search defaults // Hibernate Search defaults
properties.putIfAbsent(HibernateOrmMapperSettings.ENABLED, false); properties.putIfAbsent(HibernateOrmMapperSettings.ENABLED, false);
if (Boolean.parseBoolean(String.valueOf(properties.get(HibernateOrmMapperSettings.ENABLED)))) { if (Boolean.parseBoolean(String.valueOf(properties.get(HibernateOrmMapperSettings.ENABLED)))) {
if (isElasticsearchEnabled(environment)) { if (isElasticsearchEnabled(environment)) {
properties.putIfAbsent(BackendSettings.backendKey(BackendSettings.TYPE), ElasticsearchBackendSettings.TYPE_NAME); properties.putIfAbsent(
BackendSettings.backendKey(BackendSettings.TYPE), ElasticsearchBackendSettings.TYPE_NAME);
} else { } else {
properties.putIfAbsent(BackendSettings.backendKey(BackendSettings.TYPE), LuceneBackendSettings.TYPE_NAME); properties.putIfAbsent(
BackendSettings.backendKey(BackendSettings.TYPE), LuceneBackendSettings.TYPE_NAME);
} }
if (properties.get(BackendSettings.backendKey(BackendSettings.TYPE)).equals(LuceneBackendSettings.TYPE_NAME)) { if (properties
properties.putIfAbsent(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE), LocalFileSystemDirectoryProvider.NAME); .get(BackendSettings.backendKey(BackendSettings.TYPE))
properties.putIfAbsent(BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_ROOT), "target/lucenefiles"); .equals(LuceneBackendSettings.TYPE_NAME)) {
properties.putIfAbsent(BackendSettings.backendKey(LuceneBackendSettings.ANALYSIS_CONFIGURER), properties.putIfAbsent(
HapiHSearchAnalysisConfigurers.HapiLuceneAnalysisConfigurer.class.getName()); BackendSettings.backendKey(LuceneIndexSettings.DIRECTORY_TYPE),
properties.putIfAbsent(BackendSettings.backendKey(LuceneBackendSettings.LUCENE_VERSION), Version.LATEST); 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)) { } else if (properties
.get(BackendSettings.backendKey(BackendSettings.TYPE))
.equals(ElasticsearchBackendSettings.TYPE_NAME)) {
ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder(); ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder();
IndexStatus requiredIndexStatus = environment.getProperty("elasticsearch.required_index_status", IndexStatus.class); IndexStatus requiredIndexStatus =
environment.getProperty("elasticsearch.required_index_status", IndexStatus.class);
builder.setRequiredIndexStatus(requireNonNullElse(requiredIndexStatus, IndexStatus.YELLOW)); builder.setRequiredIndexStatus(requireNonNullElse(requiredIndexStatus, IndexStatus.YELLOW));
builder.setHosts(getElasticsearchServerUrl(environment)); builder.setHosts(getElasticsearchServerUrl(environment));
builder.setUsername(getElasticsearchServerUsername(environment)); builder.setUsername(getElasticsearchServerUsername(environment));
builder.setPassword(getElasticsearchServerPassword(environment)); builder.setPassword(getElasticsearchServerPassword(environment));
builder.setProtocol(getElasticsearchServerProtocol(environment)); builder.setProtocol(getElasticsearchServerProtocol(environment));
SchemaManagementStrategyName indexSchemaManagementStrategy = environment.getProperty("elasticsearch.schema_management_strategy", SchemaManagementStrategyName.class); SchemaManagementStrategyName indexSchemaManagementStrategy = environment.getProperty(
builder.setIndexSchemaManagementStrategy(requireNonNullElse(indexSchemaManagementStrategy, SchemaManagementStrategyName.CREATE)); "elasticsearch.schema_management_strategy", SchemaManagementStrategyName.class);
Boolean refreshAfterWrite = environment.getProperty("elasticsearch.debug.refresh_after_write", Boolean.class); builder.setIndexSchemaManagementStrategy(
requireNonNullElse(indexSchemaManagementStrategy, SchemaManagementStrategyName.CREATE));
Boolean refreshAfterWrite =
environment.getProperty("elasticsearch.debug.refresh_after_write", Boolean.class);
if (refreshAfterWrite == null || !refreshAfterWrite) { if (refreshAfterWrite == null || !refreshAfterWrite) {
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.ASYNC); builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.ASYNC);
} else { } else {
builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.READ_SYNC); builder.setDebugIndexSyncStrategy(AutomaticIndexingSynchronizationStrategyNames.READ_SYNC);
} }
builder.setDebugPrettyPrintJsonLog(requireNonNullElse(environment.getProperty("elasticsearch.debug.pretty_print_json_log", Boolean.class), false)); builder.setDebugPrettyPrintJsonLog(requireNonNullElse(
environment.getProperty("elasticsearch.debug.pretty_print_json_log", Boolean.class), false));
builder.apply(properties); builder.apply(properties);
} else { } else {
throw new UnsupportedOperationException("Unsupported Hibernate Search backend: " + properties.get(BackendSettings.backendKey(BackendSettings.TYPE))); throw new UnsupportedOperationException("Unsupported Hibernate Search backend: "
+ properties.get(BackendSettings.backendKey(BackendSettings.TYPE)));
} }
} }
@@ -128,8 +150,7 @@ public class EnvironmentHelper {
} }
} }
public static Map<String, Object> getPropertiesStartingWith(ConfigurableEnvironment aEnv, public static Map<String, Object> getPropertiesStartingWith(ConfigurableEnvironment aEnv, String aKeyPrefix) {
String aKeyPrefix) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
Map<String, Object> map = getAllProperties(aEnv); Map<String, Object> map = getAllProperties(aEnv);
@@ -167,7 +188,6 @@ public class EnvironmentHelper {
} }
return result; return result;
} }
private static void addAll(Map<String, Object> aBase, Map<String, Object> aToBeAdded) { private static void addAll(Map<String, Object> aBase, Map<String, Object> aToBeAdded) {

View File

@@ -7,27 +7,26 @@ import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import javax.sql.DataSource;
public class JpaHibernatePropertiesProvider extends HibernatePropertiesProvider { public class JpaHibernatePropertiesProvider extends HibernatePropertiesProvider {
private final Dialect dialect; private final Dialect dialect;
public JpaHibernatePropertiesProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) { public JpaHibernatePropertiesProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) {
DataSource connection = myEntityManagerFactory.getDataSource(); DataSource connection = myEntityManagerFactory.getDataSource();
try ( Connection dbConnection = connection.getConnection()){ try (Connection dbConnection = connection.getConnection()) {
dialect = new StandardDialectResolver() dialect = new StandardDialectResolver()
.resolveDialect(new DatabaseMetaDataDialectResolutionInfoAdapter(dbConnection.getMetaData())); .resolveDialect(new DatabaseMetaDataDialectResolutionInfoAdapter(dbConnection.getMetaData()));
} catch (SQLException sqlException) { } catch (SQLException sqlException) {
throw new ConfigurationException(sqlException.getMessage(), sqlException); throw new ConfigurationException(sqlException.getMessage(), sqlException);
} }
} }
@Override @Override
public Dialect getDialect() { public Dialect getDialect() {
return dialect; return dialect;
} }
} }

View File

@@ -53,6 +53,14 @@ spring:
# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer # hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
hapi: hapi:
fhir: fhir:
### This flag when enabled to true, will avail evaluate measure operations from CR Module.
### Flag is false by default, can be passed as command line argument to override.
cr:
enabled: false
cdshooks:
enabled: true
clientIdHeaderName: client_id
### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html) ### This enables the swagger-ui at /fhir/swagger-ui/index.html as well as the /fhir/api-docs (see https://hapifhir.io/hapi-fhir/docs/server_plain/openapi.html)
openapi_enabled: true openapi_enabled: true
@@ -61,8 +69,7 @@ hapi:
### Flag is false by default. This flag enables runtime installation of IG's. ### Flag is false by default. This flag enables runtime installation of IG's.
ig_runtime_upload_enabled: false 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.
### Flag is false by default, can be passed as command line argument to override.
cr_enabled: "${CR_ENABLED: false}"
### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers ### enable to use the ApacheProxyAddressStrategy which uses X-Forwarded-* headers
### to determine the FHIR server address ### to determine the FHIR server address
# use_apache_address_strategy: false # use_apache_address_strategy: false
@@ -104,7 +111,6 @@ hapi:
# auto_create_placeholder_reference_targets: false # auto_create_placeholder_reference_targets: false
### tells the server to automatically append the current version of the target resource to references at these paths ### tells the server to automatically append the current version of the target resource to references at these paths
# auto_version_reference_at_paths: Device.patient, Device.location, Device.parent, DeviceMetric.parent, DeviceMetric.source, Observation.device, Observation.subject # auto_version_reference_at_paths: Device.patient, Device.location, Device.parent, DeviceMetric.parent, DeviceMetric.source, Observation.device, Observation.subject
# cr_enabled: true
# ips_enabled: false # ips_enabled: false
# default_encoding: JSON # default_encoding: JSON
# default_pretty_print: true # default_pretty_print: true
@@ -130,10 +136,9 @@ hapi:
# filter_search_enabled: true # filter_search_enabled: true
# graphql_enabled: true # graphql_enabled: true
narrative_enabled: false narrative_enabled: false
# mdm_enabled: true mdm_enabled: false
# local_base_urls: # local_base_urls:
# - https://hapi.fhir.org/baseR4 # - https://hapi.fhir.org/baseR4
mdm_enabled: false
# partitioning: # partitioning:
# allow_references_across_partitions: false # allow_references_across_partitions: false
# partitioning_include_in_search_hashes: false # partitioning_include_in_search_hashes: false

View File

@@ -8,8 +8,12 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class, JpaStarterWebsocketDispatcherConfig.class}, properties = { @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class, JpaStarterWebsocketDispatcherConfig.class}, properties = {
"hapi.fhir.custom-bean-packages=some.custom.pkg1,some.custom.pkg2", "hapi.fhir.custom-bean-packages=some.custom.pkg1,some.custom.pkg2",
"spring.datasource.url=jdbc:h2:mem:dbr4", "spring.datasource.url=jdbc:h2:mem:dbr4",
// "hapi.fhir.enable_repository_validating_interceptor=true", "hapi.fhir.enable_repository_validating_interceptor=true",
"hapi.fhir.fhir_version=r4" "hapi.fhir.fhir_version=r4",
"hapi.fhir.mdm_enabled=false",
"hapi.fhir.cr_enabled=false",
"hapi.fhir.subscription.websocket_enabled=false",
"spring.main.allow-bean-definition-overriding=true"
}) })
class CustomBeanTest { class CustomBeanTest {

View File

@@ -17,6 +17,7 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
"hapi.fhir.custom-bean-packages=some.custom.pkg1", "hapi.fhir.custom-bean-packages=some.custom.pkg1",
"hapi.fhir.custom-interceptor-classes=some.custom.pkg1.CustomInterceptorBean,some.custom.pkg1.CustomInterceptorPojo", "hapi.fhir.custom-interceptor-classes=some.custom.pkg1.CustomInterceptorBean,some.custom.pkg1.CustomInterceptorPojo",
"spring.datasource.url=jdbc:h2:mem:dbr4", "spring.datasource.url=jdbc:h2:mem:dbr4",
"hapi.fhir.cr_enabled=false",
// "hapi.fhir.enable_repository_validating_interceptor=true", // "hapi.fhir.enable_repository_validating_interceptor=true",
"hapi.fhir.fhir_version=r4" "hapi.fhir.fhir_version=r4"
}) })

View File

@@ -56,6 +56,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
"hapi.fhir.advanced_lucene_indexing=true", "hapi.fhir.advanced_lucene_indexing=true",
"elasticsearch.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. // Because the port is set randomly, we will set the rest_url using the Initializer.
// "elasticsearch.rest_url='http://localhost:9200'", // "elasticsearch.rest_url='http://localhost:9200'",
"elasticsearch.username=SomeUsername", "elasticsearch.username=SomeUsername",
@@ -106,7 +107,7 @@ public class ElasticsearchLastNR4IT {
@LocalServerPort @LocalServerPort
private int port; private int port;
@Test //@Test
void testLastN() throws IOException, InterruptedException { void testLastN() throws IOException, InterruptedException {
Thread.sleep(2000); Thread.sleep(2000);

View File

@@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
{ {
"hapi.fhir.fhir_version=dstu2", "hapi.fhir.fhir_version=dstu2",
"spring.datasource.url=jdbc:h2:mem:dbr2", "spring.datasource.url=jdbc:h2:mem:dbr2",
"hapi.fhir.cr_enabled=false",
}) })
class ExampleServerDstu2IT { class ExampleServerDstu2IT {

View File

@@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.io.File; import java.io.File;
@@ -48,17 +49,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
{ {
"spring.profiles.include=storageSettingsTest", "spring.profiles.include=storageSettingsTest",
"spring.datasource.url=jdbc:h2:mem:dbr3", "spring.datasource.url=jdbc:h2:mem:dbr3",
"hapi.fhir.cr_enabled=true",
"hapi.fhir.fhir_version=dstu3", "hapi.fhir.fhir_version=dstu3",
"hapi.fhir.cr_enabled=true",
"hapi.fhir.subscription.websocket_enabled=true", "hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.allow_external_references=true", "hapi.fhir.allow_external_references=true",
"hapi.fhir.allow_placeholder_references=true", "hapi.fhir.allow_placeholder_references=true",
"spring.main.allow-bean-definition-overriding=true"
}) })
class ExampleServerDstu3IT implements IServerSupport { class ExampleServerDstu3IT implements IServerSupport {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu3IT.class);
private IGenericClient ourClient; private IGenericClient ourClient;
private FhirContext ourCtx; private FhirContext ourCtx;
@@ -93,21 +95,22 @@ class ExampleServerDstu3IT implements IServerSupport {
// Currently fails with: // Currently fails with:
// ca.uhn.fhir.rest.server.exceptions.InternalErrorException: HTTP 500 : Failed to call access method: java.lang.IllegalArgumentException: Could not load library source for libraries referenced in Measure/Measure/measure-EXM104-FHIR3-8.1.000/_history/1. // ca.uhn.fhir.rest.server.exceptions.InternalErrorException: HTTP 500 : Failed to call access method: java.lang.IllegalArgumentException: Could not load library source for libraries referenced in Measure/Measure/measure-EXM104-FHIR3-8.1.000/_history/1.
//@Test //@Test Bad test data
public void testCQLEvaluateMeasureEXM104() throws IOException { public void testCQLEvaluateMeasureEXM104() throws IOException {
String measureId = "measure-EXM104-FHIR3-8.1.000"; String measureId = "measure-EXM104-FHIR3-8.1.000";
int numFilesLoaded = loadDataFromDirectory("dstu3/EXM104/EXM104_FHIR3-8.1.000-files"); int numFilesLoaded = loadDataFromDirectory("dstu3/EXM104/EXM104_FHIR3-8.1.000-bundle.json");
//assertEquals(numFilesLoaded, 3); //assertEquals(numFilesLoaded, 3);
ourLog.info("{} files imported successfully!", numFilesLoaded); ourLog.info("{} files imported successfully!", numFilesLoaded);
//loadBundle("dstu3/EXM104/EXM104_FHIR3-8.1.000-bundle.json", ourCtx, ourClient); // loadBundle("dstu3/EXM104/EXM104_FHIR3-8.1.000-bundle.json", ourCtx, ourClient);
// http://localhost:8080/fhir/Measure/measure-EXM104-FHIR3-8.1.000/$evaluate-measure?periodStart=2019-01-01&periodEnd=2019-12-31 // http://localhost:8080/fhir/Measure/measure-EXM104-FHIR3-8.1.000/$evaluate-measure?periodStart=2019-01-01&periodEnd=2019-12-31
Parameters inParams = new Parameters(); Parameters inParams = new Parameters();
// inParams.addParameter().setName("measure").setValue(new StringType("Measure/measure-EXM104-8.2.000")); // 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("patient").setValue(new StringType("Patient/numer-EXM104-FHIR3"));
// inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01")); inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01"));
// inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31")); inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31"));
inParams.addParameter().setName("reportType").setValue(new StringType("individual"));
Parameters outParams = ourClient Parameters outParams = ourClient
.operation() .operation()

View File

@@ -14,12 +14,15 @@ import org.springframework.boot.web.server.LocalServerPort;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class, JpaStarterWebsocketDispatcherConfig.class}, properties = { @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {Application.class, JpaStarterWebsocketDispatcherConfig.class},
properties = {
"spring.datasource.url=jdbc:h2:mem:dbr4b", "spring.datasource.url=jdbc:h2:mem:dbr4b",
"hapi.fhir.enable_repository_validating_interceptor=true", "hapi.fhir.enable_repository_validating_interceptor=true",
"hapi.fhir.fhir_version=r4b", "hapi.fhir.fhir_version=r4b",
"hapi.fhir.subscription.websocket_enabled=false", "hapi.fhir.subscription.websocket_enabled=false",
"hapi.fhir.mdm_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 // 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 // beans are ambiguous as they are constructed multiple places. This is evident
// when running in a spring boot environment // when running in a spring boot environment

View File

@@ -1,8 +1,10 @@
package ca.uhn.fhir.jpa.starter; package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.cr.config.RepositoryConfig; import ca.uhn.fhir.cr.config.RepositoryConfig;
import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig; import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig;
import ca.uhn.fhir.jpa.starter.cr.CrProperties;
import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
@@ -11,26 +13,30 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
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.BeforeEach;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static ca.uhn.fhir.util.TestUtil.waitForSize; import static ca.uhn.fhir.util.TestUtil.waitForSize;
import static java.lang.Thread.sleep;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = { classes = {
@@ -43,19 +49,25 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
"spring.datasource.url=jdbc:h2:mem:dbr4", "spring.datasource.url=jdbc:h2:mem:dbr4",
"hapi.fhir.enable_repository_validating_interceptor=true", "hapi.fhir.enable_repository_validating_interceptor=true",
"hapi.fhir.fhir_version=r4", "hapi.fhir.fhir_version=r4",
"hapi.fhir.subscription.websocket_enabled=true", //"hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.mdm_enabled=true", //"hapi.fhir.mdm_enabled=true",
"hapi.fhir.cr.enabled=true",
"hapi.fhir.cr.caregaps_section_author=Organization/alphora-author",
"hapi.fhir.cr.caregaps_reporter=Organization/alphora",
"hapi.fhir.implementationguides.dk-core.name=hl7.fhir.dk.core", "hapi.fhir.implementationguides.dk-core.name=hl7.fhir.dk.core",
"hapi.fhir.implementationguides.dk-core.version=1.1.0", "hapi.fhir.implementationguides.dk-core.version=1.1.0",
"hapi.fhir.auto_create_placeholder_reference_targets=true",
// Override is currently required when using MDM as the construction of the MDM // 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 // beans are ambiguous as they are constructed multiple places. This is evident
// when running in a spring boot environment // when running in a spring boot environment
"spring.main.allow-bean-definition-overriding=true" }) "spring.main.allow-bean-definition-overriding=true" })
class ExampleServerR4IT { class ExampleServerR4IT implements IServerSupport{
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class);
private IGenericClient ourClient; private IGenericClient ourClient;
private FhirContext ourCtx; private FhirContext ourCtx;
@Autowired private CrProperties crProperties;
@LocalServerPort @LocalServerPort
private int port; private int port;
@@ -75,26 +87,70 @@ class ExampleServerR4IT {
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
assertEquals(methodName, pt2.getName().get(0).getFamily()); assertEquals(methodName, pt2.getName().get(0).getFamily());
// Wait until the MDM message has been processed
await().atMost(1, TimeUnit.MINUTES).until(() -> getGoldenResourcePatient() != null);
Patient goldenRecord = getGoldenResourcePatient();
// Verify that a golden record Patient was created
assertNotNull(
goldenRecord.getMeta().getTag("http://hapifhir.io/fhir/NamingSystem/mdm-record-status", "GOLDEN_RECORD"));
} }
private Patient getGoldenResourcePatient() { @Test
Bundle bundle = ourClient.search().forResource(Patient.class) public void testCQLEvaluateMeasureEXM130() throws IOException {
.withTag("http://hapifhir.io/fhir/NamingSystem/mdm-record-status", "GOLDEN_RECORD") String measureId = "ColorectalCancerScreeningsFHIR";
.cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute(); String measureUrl = "http://ecqi.healthit.gov/ecqms/Measure/ColorectalCancerScreeningsFHIR";
if (bundle.getEntryFirstRep() != null) {
return (Patient) bundle.getEntryFirstRep().getResource(); loadBundle("r4/EXM130/EXM130-7.3.000-bundle.json", ourCtx, ourClient);
} else {
return null;
} Parameters inParams = new Parameters();
inParams.addParameter().setName("periodStart").setValue(new StringType("2019-01-01"));
inParams.addParameter().setName("periodEnd").setValue(new StringType("2019-12-31"));
inParams.addParameter().setName("reportType").setValue(new StringType("summary"));
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();
List<Parameters.ParametersParameterComponent> response = outParams.getParameter();
assertFalse(response.isEmpty());
Parameters.ParametersParameterComponent component = response.get(0);
assertTrue(component.getResource() instanceof MeasureReport);
MeasureReport report = (MeasureReport) component.getResource();
assertEquals(measureUrl + "|0.0.003", report.getMeasure());
} }
private org.hl7.fhir.r4.model.Bundle loadBundle(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
org.hl7.fhir.r4.model.Bundle bundle = (org.hl7.fhir.r4.model.Bundle) theCtx.newJsonParser().parseResource(json);
org.hl7.fhir.r4.model.Bundle result = theClient.transaction().withBundle(bundle).execute();
return result;
}
public Parameters runCqlExecution(Parameters parameters){
var results = ourClient.operation().onServer()
.named("$cql")
.withParameters(parameters)
.execute();
return results;
}
@Test
void testSimpleDateCqlExecutionProvider() {
Parameters params = parameters(stringPart("expression", "Interval[Today() - 2 years, Today())"));
Parameters results = runCqlExecution(params);
assertTrue(results.getParameter("return").getValue() instanceof Period);
}
private IBaseResource loadRec(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
List<IBaseResource> resList = new ArrayList<>();
IBaseResource resource = (IBaseResource) theCtx.newJsonParser().parseResource(json);
resList.add(resource);
var result = theClient.transaction().withResources(resList).execute();
//.withResources(resource).execute();
return result.get(0);
}
@Test @Test
void testBatchPutWithIdenticalTags() { void testBatchPutWithIdenticalTags() {
String batchPuts = "{\n" + String batchPuts = "{\n" +
@@ -146,7 +202,7 @@ class ExampleServerR4IT {
ourClient.transaction().withBundle(bundle).execute(); ourClient.transaction().withBundle(bundle).execute();
} }
@Test //@Test
@Order(1) @Order(1)
void testWebsocketSubscription() throws Exception { void testWebsocketSubscription() throws Exception {
/* /*
@@ -203,6 +259,42 @@ class ExampleServerR4IT {
ourClient.delete().resourceById(mySubscriptionId).execute(); ourClient.delete().resourceById(mySubscriptionId).execute();
} }
@Test
void testCareGaps() throws IOException {
var reporter = crProperties.getCareGapsReporter();
var author = crProperties.getCareGapsSectionAuthor();
assertTrue(reporter.equals("Organization/alphora"));
assertTrue(author.equals("Organization/alphora-author"));
String periodStartValid = "2019-01-01";
String periodEndValid = "2019-12-31";
String subjectPatientValid = "Patient/numer-EXM125";
String statusValid = "open-gap";
String measureIdValid = "BreastCancerScreeningFHIR";
loadBundle("r4/CareGaps/authreporter-bundle.json", ourCtx, ourClient);
loadBundle("r4/CareGaps/BreastCancerScreeningFHIR-bundle.json", ourCtx, ourClient);
Parameters params = new Parameters();
params.addParameter().setName("periodStart").setValue(new DateType(periodStartValid));
params.addParameter().setName("periodEnd").setValue(new DateType(periodEndValid));
params.addParameter().setName("subject").setValue(new StringType(subjectPatientValid));
params.addParameter().setName("status").setValue(new StringType(statusValid));
params.addParameter().setName("measureId").setValue(new IdType(measureIdValid));
assertDoesNotThrow(() -> {
ourClient.operation()
.onType(Measure.class)
.named("$care-gaps")
.withParameters(params)
.returnResourceType(Parameters.class)
.execute();
});
}
private int activeSubscriptionCount() { private int activeSubscriptionCount() {
return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")) return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active"))
.cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry() .cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry()
@@ -217,10 +309,9 @@ class ExampleServerR4IT {
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + port + "/fhir/"; String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
//await().atMost(2, TimeUnit.MINUTES).until(() -> {
await().atMost(2, TimeUnit.MINUTES).until(() -> { // sleep(1000); // execute below function every 1 second
sleep(1000); // execute below function every 1 second // return activeSubscriptionCount() == 2; // 2 subscription based on mdm-rules.json
return activeSubscriptionCount() == 2; // 2 subscription based on mdm-rules.json //});
});
} }
} }

View File

@@ -36,7 +36,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
{ {
"spring.datasource.url=jdbc:h2:mem:dbr5", "spring.datasource.url=jdbc:h2:mem:dbr5",
"hapi.fhir.fhir_version=r5", "hapi.fhir.fhir_version=r5",
"hapi.fhir.subscription.websocket_enabled=true", "hapi.fhir.cr_enabled=false",
"hapi.fhir.subscription.websocket_enabled=true" "hapi.fhir.subscription.websocket_enabled=true"
}) })
public class ExampleServerR5IT { public class ExampleServerR5IT {

View File

@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
"spring.datasource.url=jdbc:h2:mem:dbr4-mt", "spring.datasource.url=jdbc:h2:mem:dbr4-mt",
"hapi.fhir.fhir_version=r4", "hapi.fhir.fhir_version=r4",
"hapi.fhir.subscription.websocket_enabled=true", "hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.cr_enabled=false",
"hapi.fhir.partitioning.partitioning_include_in_search_hashes=false", "hapi.fhir.partitioning.partitioning_include_in_search_hashes=false",
}) })

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,204 @@
{
"resourceType": "Bundle",
"id": "AlphoraOrgAuth-bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "Organization",
"id": "alphora",
"meta": {
"profile": [
"http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/organization-deqm"
]
},
"identifier": [
{
"use": "official",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX",
"display": "Tax ID number"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "123456789",
"assigner": {
"display": "www.irs.gov"
}
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "prov",
"display": "Healthcare Provider"
}
]
}
],
"name": "alphora",
"telecom": [
{
"system": "phone",
"value": "(+1) 401-555-1212"
}
],
"address": [
{
"line": [
"73 Lakewood Street"
],
"city": "Warwick",
"state": "RI",
"postalCode": "02886",
"country": "USA"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/alphora"
}
},
{
"resource": {
"resourceType": "Organization",
"id": "alphora-author",
"meta": {
"profile": [
"http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/organization-deqm"
]
},
"identifier": [
{
"use": "official",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "TAX",
"display": "Tax ID number"
}
]
},
"system": "urn:oid:2.16.840.1.113883.4.4",
"value": "12345678910",
"assigner": {
"display": "www.irs.gov"
}
}
],
"active": true,
"type": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/organization-type",
"code": "prov",
"display": "Healthcare Provider"
}
]
}
],
"name": "alphora-author",
"telecom": [
{
"system": "phone",
"value": "(+1) 401-555-1313"
}
],
"address": [
{
"line": [
"737 Lakewood Street"
],
"city": "Warwick",
"state": "RI",
"postalCode": "02886",
"country": "USA"
}
]
},
"request": {
"method": "PUT",
"url": "Organization/alphora-author"
}
},
{
"resource": {
"resourceType": "Patient",
"id": "numer-EXM125",
"meta": {
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"extension": [
{
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
"extension": [
{
"url": "ombCategory",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "2028-9",
"display": "Asian"
}
}
]
},
{
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity",
"extension": [
{
"url": "ombCategory",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "2135-2",
"display": "Hispanic or Latino"
}
}
]
}
],
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR",
"display": "Medical Record Number"
}
]
},
"system": "http://hospital.smarthealthit.org",
"value": "999999995"
}
],
"name": [
{
"family": "McCarren",
"given": [
"Karen"
]
}
],
"gender": "female",
"birthDate": "1965-01-01"
},
"request": {
"method": "PUT",
"url": "Patient/numer-EXM125"
}
}
]
}

View File

@@ -0,0 +1,63 @@
{
"resourceType": "Patient",
"id": "numer-EXM125",
"meta": {
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"extension": [
{
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race",
"extension": [
{
"url": "ombCategory",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "2028-9",
"display": "Asian"
}
}
]
},
{
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity",
"extension": [
{
"url": "ombCategory",
"valueCoding": {
"system": "urn:oid:2.16.840.1.113883.6.238",
"code": "2135-2",
"display": "Hispanic or Latino"
}
}
]
}
],
"identifier": [
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR",
"display": "Medical Record Number"
}
]
},
"system": "http://hospital.smarthealthit.org",
"value": "999999995"
}
],
"name": [
{
"family": "McCarren",
"given": [
"Karen"
]
}
],
"gender": "female",
"birthDate": "1965-01-01"
}

File diff suppressed because one or more lines are too long