diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnEitherVersion.java b/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnEitherVersion.java
index abf564f..463f779 100644
--- a/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnEitherVersion.java
+++ b/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnEitherVersion.java
@@ -28,6 +28,10 @@ public class OnEitherVersion extends AnyNestedCondition {
static class OnR4 {
}
+ @Conditional(OnR4BCondition.class)
+ static class OnR4B {
+ }
+
@Conditional(OnR5Condition.class)
static class OnR5 {
}
diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR4BCondition.java b/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR4BCondition.java
new file mode 100644
index 0000000..e323a22
--- /dev/null
+++ b/src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR4BCondition.java
@@ -0,0 +1,18 @@
+package ca.uhn.fhir.jpa.starter.annotations;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+public class OnR4BCondition implements Condition {
+ @Override
+ public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
+ String version = conditionContext.
+ getEnvironment()
+ .getProperty("hapi.fhir.fhir_version")
+ .toUpperCase();
+
+ return FhirVersionEnum.R4B.name().equals(version);
+ }
+}
diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4B.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4B.java
new file mode 100644
index 0000000..d99d102
--- /dev/null
+++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4B.java
@@ -0,0 +1,17 @@
+package ca.uhn.fhir.jpa.starter.common;
+
+import ca.uhn.fhir.jpa.config.r4b.JpaR4BConfig;
+import ca.uhn.fhir.jpa.starter.annotations.OnR4BCondition;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Configuration
+@Conditional(OnR4BCondition.class)
+@Import({
+ JpaR4BConfig.class,
+ StarterJpaConfig.class,
+ ElasticsearchConfig.class
+})
+public class FhirServerConfigR4B {
+}
diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java
index ed15015..3f481bd 100644
--- a/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java
+++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/StarterJpaConfig.java
@@ -26,7 +26,6 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.mdm.MdmLinkDaoJpaImpl;
import ca.uhn.fhir.jpa.dao.search.HSearchSortHelperImpl;
import ca.uhn.fhir.jpa.dao.search.IHSearchSortHelper;
-import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
@@ -423,7 +422,6 @@ public class StarterJpaConfig {
public static IServerConformanceProvider> calculateConformanceProvider(IFhirSystemDao fhirSystemDao, RestfulServer fhirServer, DaoConfig daoConfig, ISearchParamRegistry searchParamRegistry, IValidationSupport theValidationSupport) {
FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion();
if (fhirVersion == FhirVersionEnum.DSTU2) {
-
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(fhirServer, fhirSystemDao, daoConfig);
confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server");
return confProvider;
@@ -437,6 +435,11 @@ public class StarterJpaConfig {
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, daoConfig, searchParamRegistry, theValidationSupport);
confProvider.setImplementationDescription("HAPI FHIR R4 Server");
return confProvider;
+ } else if (fhirVersion == FhirVersionEnum.R4B) {
+
+ JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, daoConfig, searchParamRegistry, theValidationSupport);
+ confProvider.setImplementationDescription("HAPI FHIR R4B Server");
+ return confProvider;
} else if (fhirVersion == FhirVersionEnum.R5) {
JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(fhirServer, fhirSystemDao, daoConfig, searchParamRegistry, theValidationSupport);
diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR4B.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR4B.java
new file mode 100644
index 0000000..277af2f
--- /dev/null
+++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR4B.java
@@ -0,0 +1,77 @@
+package ca.uhn.fhir.jpa.starter.common.validation;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule;
+import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor;
+import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.starter.annotations.OnR4BCondition;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.TokenParam;
+import org.hl7.fhir.r4b.model.StructureDefinition;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR;
+
+/**
+ * This class can be customized to enable the {@link RepositoryValidatingInterceptor}
+ * on this server.
+ *
+ * The enable_repository_validating_interceptor property must be enabled in application.yaml
+ * in order to use this class.
+ */
+@ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true")
+@Configuration
+@Conditional(OnR4BCondition.class)
+public class RepositoryValidationInterceptorFactoryR4B implements IRepositoryValidationInterceptorFactory {
+
+ private final FhirContext fhirContext;
+ private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder;
+ private final IFhirResourceDao structureDefinitionResourceProvider;
+
+ public RepositoryValidationInterceptorFactoryR4B(RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) {
+ this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder;
+ this.fhirContext = daoRegistry.getSystemDao().getContext();
+ structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition");
+
+ }
+
+ @Override
+ public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() {
+
+ IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap().add(StructureDefinition.SP_KIND, new TokenParam("resource")));
+ Map> structureDefintions = results.getResources(0, results.size())
+ .stream()
+ .map(StructureDefinition.class::cast)
+ .collect(Collectors.groupingBy(StructureDefinition::getType));
+
+ structureDefintions.forEach((key, value) -> {
+ String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
+ repositoryValidatingRuleBuilder.forResourcesOfType(key).requireAtLeastOneProfileOf(urls).and().requireValidationToDeclaredProfiles();
+ });
+
+ List rules = repositoryValidatingRuleBuilder.build();
+ return new RepositoryValidatingInterceptor(fhirContext, rules);
+ }
+
+ @Override
+ public RepositoryValidatingInterceptor build() {
+
+ // Customize the ruleBuilder here to have the rules you want! We will give a simple example
+ // of enabling validation for all Patient resources
+ repositoryValidatingRuleBuilder.forResourcesOfType("Patient").requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient").and().requireValidationToDeclaredProfiles();
+
+ // Do not customize below this line
+ List rules = repositoryValidatingRuleBuilder.build();
+ return new RepositoryValidatingInterceptor(fhirContext, rules);
+ }
+
+}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 19a1666..67a652f 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -48,8 +48,8 @@ spring:
# hibernate.search.backend.directory.root: target/lucenefiles
# hibernate.search.backend.lucene_version: lucene_current
### elastic parameters ===> see also elasticsearch section below <===
- hibernate.search.backend.type: elasticsearch
- hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
+# hibernate.search.backend.type: elasticsearch
+# hibernate.search.backend.analysis.configurer: ca.uhn.fhir.jpa.search.HapiHSearchAnalysisConfigurers$HapiElasticAnalysisConfigurer
hapi:
fhir:
### 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)
diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4BIT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4BIT.java
new file mode 100644
index 0000000..0092722
--- /dev/null
+++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4BIT.java
@@ -0,0 +1,120 @@
+package ca.uhn.fhir.jpa.starter;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4b.model.Bundle;
+import org.hl7.fhir.r4b.model.Patient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = {
+ "spring.datasource.url=jdbc:h2:mem:dbr4b",
+ "hapi.fhir.enable_repository_validating_interceptor=true",
+ "hapi.fhir.fhir_version=r4b",
+ "hapi.fhir.subscription.websocket_enabled=false",
+ "hapi.fhir.mdm_enabled=false",
+ "hapi.fhir.implementationguides.dk-core.name=hl7.fhir.dk.core",
+ "hapi.fhir.implementationguides.dk-core.version=1.1.0",
+ // Override is currently required when using MDM as the construction of the MDM
+ // beans are ambiguous as they are constructed multiple places. This is evident
+ // when running in a spring boot environment
+ "spring.main.allow-bean-definition-overriding=true"})
+class ExampleServerR4BIT {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4BIT.class);
+ private IGenericClient ourClient;
+ private FhirContext ourCtx;
+
+ @LocalServerPort
+ private int port;
+
+ @Test
+ @Order(0)
+ void testCreateAndRead() {
+ String methodName = "testCreateAndRead";
+ ourLog.info("Entering " + methodName + "()...");
+
+ Patient pt = new Patient();
+ pt.setActive(true);
+ pt.getBirthDateElement().setValueAsString("2020-01-01");
+ pt.addIdentifier().setSystem("http://foo").setValue("12345");
+ pt.addName().setFamily(methodName);
+ IIdType id = ourClient.create().resource(pt).execute().getId();
+
+ Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
+ assertEquals(methodName, pt2.getName().get(0).getFamily());
+
+ }
+
+
+ @Test
+ public void testBatchPutWithIdenticalTags() {
+ String batchPuts = "{\n" +
+ "\t\"resourceType\": \"Bundle\",\n" +
+ "\t\"id\": \"patients\",\n" +
+ "\t\"type\": \"batch\",\n" +
+ "\t\"entry\": [\n" +
+ "\t\t{\n" +
+ "\t\t\t\"request\": {\n" +
+ "\t\t\t\t\"method\": \"PUT\",\n" +
+ "\t\t\t\t\"url\": \"Patient/pat-1\"\n" +
+ "\t\t\t},\n" +
+ "\t\t\t\"resource\": {\n" +
+ "\t\t\t\t\"resourceType\": \"Patient\",\n" +
+ "\t\t\t\t\"id\": \"pat-1\",\n" +
+ "\t\t\t\t\"meta\": {\n" +
+ "\t\t\t\t\t\"tag\": [\n" +
+ "\t\t\t\t\t\t{\n" +
+ "\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" +
+ "\t\t\t\t\t\t\t\"code\": \"value2\"\n" +
+ "\t\t\t\t\t\t}\n" +
+ "\t\t\t\t\t]\n" +
+ "\t\t\t\t}\n" +
+ "\t\t\t},\n" +
+ "\t\t\t\"fullUrl\": \"/Patient/pat-1\"\n" +
+ "\t\t},\n" +
+ "\t\t{\n" +
+ "\t\t\t\"request\": {\n" +
+ "\t\t\t\t\"method\": \"PUT\",\n" +
+ "\t\t\t\t\"url\": \"Patient/pat-2\"\n" +
+ "\t\t\t},\n" +
+ "\t\t\t\"resource\": {\n" +
+ "\t\t\t\t\"resourceType\": \"Patient\",\n" +
+ "\t\t\t\t\"id\": \"pat-2\",\n" +
+ "\t\t\t\t\"meta\": {\n" +
+ "\t\t\t\t\t\"tag\": [\n" +
+ "\t\t\t\t\t\t{\n" +
+ "\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" +
+ "\t\t\t\t\t\t\t\"code\": \"value2\"\n" +
+ "\t\t\t\t\t\t}\n" +
+ "\t\t\t\t\t]\n" +
+ "\t\t\t\t}\n" +
+ "\t\t\t},\n" +
+ "\t\t\t\"fullUrl\": \"/Patient/pat-2\"\n" +
+ "\t\t}\n" +
+ "\t]\n" +
+ "}";
+ Bundle bundle = FhirContext.forR4B().newJsonParser().parseResource(Bundle.class, batchPuts);
+ ourClient.transaction().withBundle(bundle).execute();
+ }
+
+
+
+ @BeforeEach
+ void beforeEach() {
+
+ ourCtx = FhirContext.forR4B();
+ ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
+ String ourServerBase = "http://localhost:" + port + "/fhir/";
+ ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
+
+
+ }
+}