diff --git a/.travis.yml b/.travis.yml index 7cd8c41..54d387b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,4 +29,7 @@ before_script: - sudo chown -R travis:travis "$HOME/.m2/repository"; script: - - mvn install + - mvn -U install + + + diff --git a/pom.xml b/pom.xml index 0e272c5..9df45f1 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.8.0 + 4.0.0-SNAPSHOT ca.uhn.hapi.fhir.demo @@ -138,11 +138,11 @@ - - org.apache.derby - derby + com.h2database + h2 @@ -198,6 +198,13 @@ provided + + ca.uhn.hapi.fhir + hapi-fhir-test-utilities + ${project.version} + test + + diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java b/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java index ddddbe0..24b4f79 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java @@ -13,6 +13,8 @@ public class ApplicationContext extends AnnotationConfigWebApplicationContext { register(FhirServerConfigDstu3.class, FhirServerConfigCommon.class); } else if (fhirVersion == FhirVersionEnum.R4) { register(FhirServerConfigR4.class, FhirServerConfigCommon.class); + } else if (fhirVersion == FhirVersionEnum.R5) { + register(FhirServerConfigR5.class, FhirServerConfigCommon.class); } else { throw new IllegalStateException(); } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java index e44faac..a396ee4 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java @@ -1,15 +1,18 @@ package ca.uhn.fhir.jpa.starter; +import ca.uhn.fhir.jpa.binstore.DatabaseBlobBinaryStorageSvcImpl; +import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; import org.apache.commons.dbcp2.BasicDataSource; -import org.hl7.fhir.instance.model.Subscription; +import org.hl7.fhir.dstu2.model.Subscription; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thymeleaf.util.Validate; @@ -108,6 +111,8 @@ public class FhirServerConfigCommon { retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); } + retVal.setFilterParameterEnabled(HapiProperties.getFilterSearchEnabled()); + return retVal; } @@ -149,6 +154,12 @@ public class FhirServerConfigCommon { return retVal; } + @Lazy + @Bean + public IBinaryStorageSvc binaryStorageSvc() { + return new DatabaseBlobBinaryStorageSvcImpl(); + } + @Bean() public IEmailSender emailSender() { if (this.emailEnabled) { diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java new file mode 100644 index 0000000..e176080 --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java @@ -0,0 +1,58 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; +import ca.uhn.fhir.jpa.config.BaseJavaConfigR5; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +@Configuration +public class FhirServerConfigR5 extends BaseJavaConfigR5 { + + @Autowired + private DataSource myDataSource; + + /** + * We override the paging provider definition so that we can 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. + */ + @Override + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider(); + pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize()); + pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize()); + return pagingProvider; + } + + @Override + @Bean() + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); + retVal.setPersistenceUnitName("HAPI_PU"); + + try { + retVal.setDataSource(myDataSource); + } catch (Exception e) { + throw new ConfigurationException("Could not set the data source due to a configuration issue", e); + } + + retVal.setJpaProperties(HapiProperties.getProperties()); + return retVal; + } + + @Bean() + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } + +} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java index 40faec3..3dac8f4 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java @@ -6,11 +6,17 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; import com.google.common.annotations.VisibleForTesting; -import java.io.InputStream; import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Arrays; import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.StringUtils.*; public class HapiProperties { + public static final String BINARY_STORAGE_ENABLED = "binary_storage.enabled"; static final String ALLOW_EXTERNAL_REFERENCES = "allow_external_references"; static final String ALLOW_MULTIPLE_DELETE = "allow_multiple_delete"; static final String ALLOW_PLACEHOLDER_REFERENCES = "allow_placeholder_references"; @@ -25,6 +31,7 @@ public class HapiProperties { static final String DEFAULT_PRETTY_PRINT = "default_pretty_print"; static final String ETAG_SUPPORT = "etag_support"; static final String FHIR_VERSION = "fhir_version"; + static final String ALLOW_CASCADING_DELETES = "allow_cascading_deletes"; static final String HAPI_PROPERTIES = "hapi.properties"; static final String LOGGER_ERROR_FORMAT = "logger.error_format"; static final String LOGGER_FORMAT = "logger.format"; @@ -46,7 +53,10 @@ public class HapiProperties { static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches"; static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params"; static final String EMAIL_FROM = "email.from"; - + private static final String VALIDATE_REQUESTS_ENABLED = "validation.requests.enabled"; + private static final String VALIDATE_RESPONSES_ENABLED = "validation.responses.enabled"; + private static final String FILTER_SEARCH_ENABLED = "filter_search.enabled"; + private static final String GRAPHQL_ENABLED = "graphql.enabled"; private static Properties properties; /* @@ -69,7 +79,7 @@ public class HapiProperties { public static Properties getProperties() { if (properties == null) { // Load the configurable properties file - try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)){ + try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)) { HapiProperties.properties = new Properties(); HapiProperties.properties.load(in); } catch (Exception e) { @@ -77,8 +87,8 @@ public class HapiProperties { } Properties overrideProps = loadOverrideProperties(); - if(overrideProps != null) { - properties.putAll(overrideProps); + if (overrideProps != null) { + properties.putAll(overrideProps); } } @@ -88,17 +98,17 @@ public class HapiProperties { /** * If a configuration file path is explicitly specified via -Dhapi.properties=, the properties there will * be used to override the entries in the default hapi.properties file (currently under WEB-INF/classes) + * * @return properties loaded from the explicitly specified configuraiton file if there is one, or null otherwise. */ private static Properties loadOverrideProperties() { String confFile = System.getProperty(HAPI_PROPERTIES); - if(confFile != null) { + if (confFile != null) { try { Properties props = new Properties(); props.load(new FileInputStream(confFile)); return props; - } - catch (Exception e) { + } catch (Exception e) { throw new ConfigurationException("Could not load HAPI properties file: " + confFile, e); } } @@ -140,6 +150,10 @@ public class HapiProperties { return Boolean.parseBoolean(value); } + private static boolean getBooleanProperty(String propertyName, boolean defaultValue) { + return getBooleanProperty(propertyName, Boolean.valueOf(defaultValue)); + } + private static Integer getIntegerProperty(String propertyName, Integer defaultValue) { String value = HapiProperties.getProperty(propertyName); @@ -160,6 +174,10 @@ public class HapiProperties { return FhirVersionEnum.DSTU3; } + public static boolean isBinaryStorageEnabled() { + return HapiProperties.getBooleanProperty(BINARY_STORAGE_ENABLED, true); + } + public static ETagSupportEnum getEtagSupport() { String etagSupportString = HapiProperties.getProperty(ETAG_SUPPORT); @@ -240,6 +258,10 @@ public class HapiProperties { return HapiProperties.getBooleanProperty(ALLOW_MULTIPLE_DELETE, false); } + public static Boolean getAllowCascadingDeletes() { + return HapiProperties.getBooleanProperty(ALLOW_CASCADING_DELETES, false); + } + public static Boolean getAllowExternalReferences() { return HapiProperties.getBooleanProperty(ALLOW_EXTERNAL_REFERENCES, false); } @@ -264,6 +286,14 @@ public class HapiProperties { return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*"); } + public static Set getSupportedResourceTypes() { + String[] types = defaultString(getProperty("supported_resource_types")).split(","); + return Arrays.stream(types) + .map(t -> trim(t)) + .filter(t -> isNotBlank(t)) + .collect(Collectors.toSet()); + } + public static String getServerName() { return HapiProperties.getProperty(SERVER_NAME, "Local Tester"); } @@ -328,4 +358,22 @@ public class HapiProperties { public static Boolean getCorsAllowedCredentials() { return HapiProperties.getBooleanProperty(CORS_ALLOW_CREDENTIALS, false); } + + public static boolean getValidateRequestsEnabled() { + return HapiProperties.getBooleanProperty(VALIDATE_REQUESTS_ENABLED, false); + } + + public static boolean getValidateResponsesEnabled() { + return HapiProperties.getBooleanProperty(VALIDATE_RESPONSES_ENABLED, false); + } + + public static boolean getFilterSearchEnabled() { + return HapiProperties.getBooleanProperty(FILTER_SEARCH_ENABLED, true); + } + + public static boolean getGraphqlEnabled() { + return HapiProperties.getBooleanProperty(GRAPHQL_ENABLED, true); + } + } + diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java index ebfbad9..a640740 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -2,18 +2,20 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorService; +import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; -import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; -import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; +import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.provider.*; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; -import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; +import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5; +import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor; @@ -22,9 +24,9 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.rest.server.interceptor.*; +import ca.uhn.fhir.validation.IValidatorModule; +import ca.uhn.fhir.validation.ResultSeverityEnum; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Meta; import org.springframework.context.ApplicationContext; @@ -33,6 +35,8 @@ import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; import java.util.Arrays; +import java.util.Collections; +import java.util.Set; public class JpaRestfulServer extends RestfulServer { @@ -48,6 +52,12 @@ public class JpaRestfulServer extends RestfulServer { * specified in the properties file. */ ApplicationContext appCtx = (ApplicationContext) getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); + // Customize supported resource types + Set supportedResourceTypes = HapiProperties.getSupportedResourceTypes(); + if (!supportedResourceTypes.isEmpty()) { + DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class); + daoRegistry.setSupportedResourceTypes(supportedResourceTypes); + } /* * ResourceProviders are fetched from the Spring context @@ -64,6 +74,9 @@ public class JpaRestfulServer extends RestfulServer { } else if (fhirVersion == FhirVersionEnum.R4) { resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); + } else if (fhirVersion == FhirVersionEnum.R5) { + resourceProviders = appCtx.getBean("myResourceProvidersR5", ResourceProviderFactory.class); + systemProvider = appCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class); } else { throw new IllegalStateException(); } @@ -96,6 +109,11 @@ public class JpaRestfulServer extends RestfulServer { JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, appCtx.getBean(DaoConfig.class)); confProvider.setImplementationDescription("HAPI FHIR R4 Server"); setServerConformanceProvider(confProvider); + } else if (fhirVersion == FhirVersionEnum.R5) { + IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoR5", IFhirSystemDao.class); + JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, appCtx.getBean(DaoConfig.class)); + confProvider.setImplementationDescription("HAPI FHIR R5 Server"); + setServerConformanceProvider(confProvider); } else { throw new IllegalStateException(); } @@ -167,11 +185,7 @@ public class JpaRestfulServer extends RestfulServer { * with this feature. */ if (false) { // <-- DISABLED RIGHT NOW - if (fhirVersion == FhirVersionEnum.DSTU3) { - registerProvider(appCtx.getBean(TerminologyUploaderProviderDstu3.class)); - } else if (fhirVersion == FhirVersionEnum.R4) { - registerProvider(appCtx.getBean(TerminologyUploaderProviderR4.class)); - } + registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); } // If you want to enable the $trigger-subscription operation to allow @@ -226,6 +240,58 @@ public class JpaRestfulServer extends RestfulServer { interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); } + // Cascading deletes + DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class); + IInterceptorBroadcaster interceptorBroadcaster = appCtx.getBean(IInterceptorBroadcaster.class); + if (HapiProperties.getAllowCascadingDeletes()) { + CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(daoRegistry, interceptorBroadcaster); + getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); + } + + // Binary Storage + if (HapiProperties.isBinaryStorageEnabled()) { + BinaryStorageInterceptor binaryStorageInterceptor = appCtx.getBean(BinaryStorageInterceptor.class); + getInterceptorService().registerInterceptor(binaryStorageInterceptor); + } + + // Validation + IValidatorModule validatorModule; + switch (fhirVersion) { + case DSTU3: + validatorModule = appCtx.getBean("myInstanceValidatorDstu3", IValidatorModule.class); + break; + case R4: + validatorModule = appCtx.getBean("myInstanceValidatorR4", IValidatorModule.class); + break; + case R5: + validatorModule = appCtx.getBean("myInstanceValidatorR5", IValidatorModule.class); + break; + default: + validatorModule = null; + break; + } + if (validatorModule != null) { + if (HapiProperties.getValidateRequestsEnabled()) { + RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); + interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + interceptor.setValidatorModules(Collections.singletonList(validatorModule)); + registerInterceptor(interceptor); + } + if (HapiProperties.getValidateResponsesEnabled()) { + ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor(); + interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + interceptor.setValidatorModules(Collections.singletonList(validatorModule)); + registerInterceptor(interceptor); + } + } + + // GraphQL + if (HapiProperties.getGraphqlEnabled()) { + if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { + registerProvider(appCtx.getBean(GraphQLProvider.class)); + } + } + } } diff --git a/src/main/resources/hapi.properties b/src/main/resources/hapi.properties index 6a34a06..0f9bd81 100644 --- a/src/main/resources/hapi.properties +++ b/src/main/resources/hapi.properties @@ -22,6 +22,7 @@ allow_override_default_search_params=true allow_contains_searches=true allow_multiple_delete=true allow_external_references=true +allow_cascading_deletes=true allow_placeholder_references=true expunge_enabled=true persistence_unit_name=HAPI_PU @@ -29,14 +30,40 @@ logger.name=fhirtest.access logger.format=Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}] logger.error_format=ERROR - ${requestVerb} ${requestUrl} logger.log_exceptions=true -datasource.driver=org.apache.derby.jdbc.EmbeddedDriver -datasource.url=jdbc:derby:directory:target/jpaserver_derby_files;create=true +datasource.driver=org.h2.Driver +datasource.url=jdbc:h2:file:./target/database/h2 datasource.username= datasource.password= server.name=Local Tester server.id=home test.port= -hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect + +################################################### +# Validation +################################################### +# Should all incoming requests be validated +validation.requests.enabled=false +# Should outgoing responses be validated +validation.responses.enabled=false + +################################################### +# Search Features +################################################### +filter_search.enabled=true +graphql.enabled=true + +################################################### +# Supported Resources +################################################### +# Enable the following property if you want to customize the +# list of resources that is supported by the server (i.e. to +# disable specific resources) +#supported_resource_types=Patient,Observation,Encounter + +################################################### +# Database Settings +################################################### +hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory hibernate.format_sql=false hibernate.show_sql=false @@ -50,6 +77,15 @@ hibernate.search.default.directory_provider=filesystem hibernate.search.default.indexBase=target/lucenefiles hibernate.search.lucene_version=LUCENE_CURRENT tester.config.refuse_to_fetch_third_party_urls=false + +################################################## +# Binary Storage Operations +################################################## +binary_storage.enabled=true + +################################################## +# CORS Settings +################################################## cors.enabled=true cors.allowCredentials=true # Supports multiple, comma separated allowed origin entries diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java index 70b4e5f..ee6ebf8 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java @@ -5,7 +5,7 @@ import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.test.utilities.JettyUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.hl7.fhir.instance.model.api.IIdType; @@ -23,16 +23,13 @@ public class ExampleServerDstu2IT { private static IGenericClient ourClient; private static FhirContext ourCtx; private static int ourPort; - private static Server ourServer; - private static String ourServerBase; static { HapiProperties.forceReload(); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU2"); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr2;create=true"); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr2"); ourCtx = FhirContext.forDstu2(); - ourPort = PortUtil.findFreePort(); } @Test @@ -59,7 +56,7 @@ public class ExampleServerDstu2IT { ourLog.info("Project base path is: {}", path); - ourServer = new Server(ourPort); + ourServer = new Server(0); WebAppContext webAppContext = new WebAppContext(); webAppContext.setContextPath("/hapi-fhir-jpaserver"); @@ -70,9 +67,11 @@ public class ExampleServerDstu2IT { ourServer.setHandler(webAppContext); ourServer.start(); + ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient.registerInterceptor(new LoggingInterceptor(true)); } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java index 4580f9c..7507bfb 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java @@ -1,18 +1,13 @@ package ca.uhn.fhir.jpa.starter; -import static ca.uhn.fhir.util.TestUtil.waitForSize; -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.net.URI; -import java.nio.file.Paths; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.JettyUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.websocket.api.Session; @@ -23,12 +18,17 @@ import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import java.net.URI; +import java.nio.file.Paths; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static ca.uhn.fhir.util.TestUtil.waitForSize; +import static org.junit.Assert.assertEquals; public class ExampleServerDstu3IT { @@ -36,17 +36,14 @@ public class ExampleServerDstu3IT { private static IGenericClient ourClient; private static FhirContext ourCtx; private static int ourPort; - private static Server ourServer; - private static String ourServerBase; static { HapiProperties.forceReload(); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU3"); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr3;create=true"); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr3"); HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); ourCtx = FhirContext.forDstu3(); - ourPort = PortUtil.findFreePort(); } @Test @@ -131,10 +128,7 @@ public class ExampleServerDstu3IT { ourLog.info("Project base path is: {}", path); - if (ourPort == 0) { - ourPort = RandomServerPortProvider.findFreePort(); - } - ourServer = new Server(ourPort); + ourServer = new Server(0); WebAppContext webAppContext = new WebAppContext(); webAppContext.setContextPath("/hapi-fhir-jpaserver"); @@ -145,9 +139,11 @@ public class ExampleServerDstu3IT { ourServer.setHandler(webAppContext); ourServer.start(); + ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient.registerInterceptor(new LoggingInterceptor(true)); } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java index bff12bb..1eedafd 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.test.utilities.JettyUtil; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.websocket.api.Session; @@ -36,22 +36,19 @@ public class ExampleServerR4IT { private static IGenericClient ourClient; private static FhirContext ourCtx; private static int ourPort; - private static Server ourServer; - private static String ourServerBase; static { HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:derby:memory:dbr4;create=true"); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4"); HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); ourCtx = FhirContext.forR4(); - ourPort = PortUtil.findFreePort(); } @Test public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); String methodName = "testCreateResourceConditional"; Patient pt = new Patient(); @@ -131,10 +128,7 @@ public class ExampleServerR4IT { ourLog.info("Project base path is: {}", path); - if (ourPort == 0) { - ourPort = RandomServerPortProvider.findFreePort(); - } - ourServer = new Server(ourPort); + ourServer = new Server(0); WebAppContext webAppContext = new WebAppContext(); webAppContext.setContextPath("/hapi-fhir-jpaserver"); @@ -146,9 +140,11 @@ public class ExampleServerR4IT { ourServer.setHandler(webAppContext); ourServer.start(); + ourPort = JettyUtil.getPortForStartedServer(ourServer); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - ourServerBase = HapiProperties.getServerAddress(); + String ourServerBase = HapiProperties.getServerAddress(); ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java new file mode 100644 index 0000000..04aee05 --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java @@ -0,0 +1,157 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.test.utilities.JettyUtil; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.webapp.WebAppContext; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.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.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.URI; +import java.nio.file.Paths; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static ca.uhn.fhir.util.TestUtil.waitForSize; +import static org.junit.Assert.assertEquals; + +public class ExampleServerR5IT { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR5IT.class); + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static int ourPort; + private static Server ourServer; + + static { + HapiProperties.forceReload(); + HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr5"); + HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R5"); + HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); + ourCtx = FhirContext.forR4(); + } + + @Test + public void testCreateAndRead() { + ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + String methodName = "testCreateResourceConditional"; + + Patient pt = new Patient(); + 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 testWebsocketSubscription() throws Exception { + /* + * Create subscription + */ + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); + subscription.setCriteria("Observation?status=final"); + + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); + channel.setPayload("application/json"); + subscription.setChannel(channel); + + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + IIdType mySubscriptionId = methodOutcome.getId(); + + // Wait for the subscription to be activated + waitForSize(1, () -> ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size()); + + /* + * Attach websocket + */ + + WebSocketClient myWebSocketClient = new WebSocketClient(); + SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); + + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + ourLog.info("Connecting to : {}", echoUri); + Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); + Session session = connection.get(2, TimeUnit.SECONDS); + + ourLog.info("Connected to WS: {}", session.isOpen()); + + /* + * Create a matching resource + */ + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + ourClient.create().resource(obs).execute(); + + // Give some time for the subscription to deliver + Thread.sleep(2000); + + /* + * Ensure that we receive a ping on the websocket + */ + waitForSize(1, () -> mySocketImplementation.myPingCount); + + /* + * Clean up + */ + ourClient.delete().resourceById(mySubscriptionId).execute(); + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + String path = Paths.get("").toAbsolutePath().toString(); + + ourLog.info("Project base path is: {}", path); + + ourServer = new Server(0); + + WebAppContext webAppContext = new WebAppContext(); + webAppContext.setContextPath("/hapi-fhir-jpaserver"); + webAppContext.setDisplayName("HAPI FHIR"); + webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); + webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); + webAppContext.setParentLoaderPriority(true); + + ourServer.setHandler(webAppContext); + ourServer.start(); + + ourPort = JettyUtil.getPortForStartedServer(ourServer); + + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } + + public static void main(String[] theArgs) throws Exception { + ourPort = 8080; + beforeClass(); + } +}