From 30c391db00c044615c42e2f137aad3b8a97cd206 Mon Sep 17 00:00:00 2001 From: Mathieu Ouellet Date: Tue, 22 Aug 2017 06:57:00 -0400 Subject: [PATCH 01/24] Add Spring Boot Starter Provides spring-boot-starter with auto-configurations for Rest server, Jpa server, FHIR Validation and clients. Default configurations overridable by configuration properties and rest-server customizer. --- .gitignore | 2 + .../pom.xml | 118 +++++++ .../autoconfigure/FhirAutoConfiguration.java | 287 ++++++++++++++++++ .../boot/autoconfigure/FhirProperties.java | 85 ++++++ .../FhirRestfulServerCustomizer.java | 13 + .../main/resources/META-INF/spring.factories | 1 + .../FhirAutoConfigurationTest.java | 170 +++++++++++ .../src/test/resources/application.yml | 13 + .../src/test/resources/logback-test.xml | 4 + .../pom.xml | 64 ++++ .../SampleApacheRestfulClientApplication.java | 37 +++ .../src/main/resources/application.yml | 13 + .../pom.xml | 64 ++++ .../SampleOkHttpRestfulClientApplication.java | 37 +++ .../src/main/resources/application.yml | 13 + .../pom.xml | 67 ++++ .../SampleJerseyRestfulServerApplication.java | 20 ++ .../provider/PatientResourceProvider.java | 73 +++++ .../src/main/resources/application.yml | 21 ++ ...pleJerseyRestfulServerApplicationTest.java | 41 +++ .../pom.xml | 75 +++++ .../SampleJpaRestfulServerApplication.java | 12 + .../src/main/resources/application.yml | 37 +++ ...SampleJpaRestfulServerApplicationTest.java | 41 +++ .../hapi-fhir-spring-boot-samples/pom.xml | 21 ++ .../hapi-fhir-spring-boot-starter/pom.xml | 27 ++ hapi-fhir-spring-boot/pom.xml | 21 ++ pom.xml | 9 + 28 files changed, 1386 insertions(+) create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml create mode 100644 hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml create mode 100644 hapi-fhir-spring-boot/pom.xml diff --git a/.gitignore b/.gitignore index 09734b19d32..db79bca9c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -141,6 +141,8 @@ local.properties **/.target **/.project **/.classpath +**/.factorypath +**/.springBeans # PDT-specific diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml new file mode 100644 index 00000000000..b66479550f3 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-autoconfigure + + jar + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + ca.uhn.hapi.fhir + hapi-fhir-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-server + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client + ${project.version} + true + + + ca.uhn.hapi.fhir + hapi-fhir-client-okhttp + ${project.version} + true + + + javax.servlet + javax.servlet-api + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-web + test + + + com.h2database + h2 + test + + + ch.qos.logback + logback-classic + test + + + org.slf4j + log4j-over-slf4j + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2.1 + ${project.version} + test + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + test + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java new file mode 100644 index 00000000000..3a108336b07 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -0,0 +1,287 @@ +package ca.uhn.fhir.spring.boot.autoconfigure; + +import java.util.List; + +import javax.servlet.ServletException; +import javax.sql.DataSource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.provider.BaseJpaProvider; +import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; +import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; +import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; +import okhttp3.OkHttpClient; +import org.apache.http.client.HttpClient; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ResourceCondition; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +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.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.util.CollectionUtils; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. + * + * @author Mathieu Ouellet + */ +@Configuration +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) +@EnableConfigurationProperties(FhirProperties.class) +public class FhirAutoConfiguration { + + private final FhirProperties properties; + + public FhirAutoConfiguration(FhirProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean + public FhirContext fhirContext() { + FhirContext fhirContext = new FhirContext(properties.getVersion()); + return fhirContext; + } + + @Configuration + @ConditionalOnClass(AbstractJaxRsProvider.class) + @EnableConfigurationProperties(FhirProperties.class) + @ConfigurationProperties("hapi.fhir.rest") + @SuppressWarnings("serial") + static class FhirRestfulServerConfiguration extends RestfulServer { + + private final FhirProperties properties; + + private final FhirContext fhirContext; + + private final List resourceProviders; + + private final IPagingProvider pagingProvider; + + private final List interceptors; + + private final List customizers; + + public FhirRestfulServerConfiguration( + FhirProperties properties, + FhirContext fhirContext, + ObjectProvider> resourceProviders, + ObjectProvider pagingProvider, + ObjectProvider> interceptors, + ObjectProvider> customizers) { + this.properties = properties; + this.fhirContext = fhirContext; + this.resourceProviders = resourceProviders.getIfAvailable(); + this.pagingProvider = pagingProvider.getIfAvailable(); + this.interceptors = interceptors.getIfAvailable(); + this.customizers = customizers.getIfAvailable(); + } + + @Bean + public ServletRegistrationBean fhirServerRegistrationBean() { + ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); + registration.setLoadOnStartup(1); + return registration; + } + + @Override + protected void initialize() throws ServletException { + super.initialize(); + + setFhirContext(this.fhirContext); + setResourceProviders(this.resourceProviders); + setPagingProvider(this.pagingProvider); + setInterceptors(this.interceptors); + + setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); + + customize(); + } + + private void customize() { + if (this.customizers != null) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (FhirRestfulServerCustomizer customizer : this.customizers) { + customizer.customize(this); + } + } + } + } + + @Configuration + @ConditionalOnClass(BaseJpaProvider.class) + @ConditionalOnBean(DataSource.class) + @EnableConfigurationProperties(FhirProperties.class) + static class FhirJpaServerConfiguration { + + @Configuration + @EntityScan("ca.uhn.fhir.jpa.entity") + @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") + static class FhirJpaDaoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.jpa") + public DaoConfig fhirDaoConfig() { + DaoConfig fhirDaoConfig = new DaoConfig(); + return fhirDaoConfig; + } + } + + @Configuration + @ConditionalOnBean({ DaoConfig.class, RestfulServer.class }) + @SuppressWarnings("rawtypes") + static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { + + private final BaseJpaSystemProvider systemProviders; + + public RestfulServerCustomizer(ObjectProvider systemProviders) { + this.systemProviders = systemProviders.getIfAvailable(); + } + + @Override + public void customize(RestfulServer server) { + server.setPlainProviders(systemProviders); + } + } + + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") + static class Dstu3 extends BaseJavaConfigDstu3 { + } + + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") + static class Dstu2 extends BaseJavaConfigDstu2 { + } + } + + @Configuration + @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) + @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) + static class FhirValidationConfiguration { + + @Bean + @ConditionalOnMissingBean + public RequestValidatingInterceptor requestValidatingInterceptor() { + return new RequestValidatingInterceptor(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") + public ResponseValidatingInterceptor responseValidatingInterceptor() { + return new ResponseValidatingInterceptor(); + } + + static class SchemaAvailableCondition extends ResourceCondition { + + SchemaAvailableCondition() { + super("ValidationSchema", + "hapi.fhir.validation", + "schema-location", + "classpath:/org/hl7/fhir/instance/model/schema", + "classpath:/org/hl7/fhir/dstu2016may/model/schema", + "classpath:/org/hl7/fhir/dstu3/model/schema"); + } + } + } + + @Configuration + @ConditionalOnProperty("hapi.fhir.server.url") + @EnableConfigurationProperties(FhirProperties.class) + static class FhirRestfulClientConfiguration { + + private final FhirProperties properties; + + private final List clientInterceptors; + + public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> clientInterceptors) { + this.properties = properties; + this.clientInterceptors = clientInterceptors.getIfAvailable(); + } + + @Bean + @ConditionalOnBean(IRestfulClientFactory.class) + public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { + IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); + if (!CollectionUtils.isEmpty(this.clientInterceptors)) { + for (IClientInterceptor interceptor : this.clientInterceptors) { + fhirClient.registerInterceptor(interceptor); + } + } + return fhirClient; + } + + @Configuration + @ConditionalOnClass(HttpClient.class) + @ConditionalOnMissingClass("okhttp3.OkHttpClient") + static class Apache { + + private final FhirContext context; + + public Apache(FhirContext context) { + this.context = context; + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.apache") + public IRestfulClientFactory fhirRestfulClientFactory() { + ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + + @Configuration + @ConditionalOnClass(OkHttpClient.class) + static class OkHttp { + + private final FhirContext context; + + public OkHttp(FhirContext context) { + this.context = context; + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.okhttp") + public IRestfulClientFactory fhirRestfulClientFactory() { + OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java new file mode 100644 index 00000000000..5bb8d3cc2f5 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirProperties.java @@ -0,0 +1,85 @@ +package ca.uhn.fhir.spring.boot.autoconfigure; + +import ca.uhn.fhir.context.FhirVersionEnum; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "hapi.fhir") +public class FhirProperties { + + private FhirVersionEnum version = FhirVersionEnum.DSTU2; + + private Server server = new Server(); + + private Validation validation = new Validation(); + + public FhirVersionEnum getVersion() { + return version; + } + + public void setVersion(FhirVersionEnum version) { + this.version = version; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } + + public Validation getValidation() { + return validation; + } + + public void setValidation(Validation validation) { + this.validation = validation; + } + + public static class Server { + + private String url; + + private String path = "/fhir/*"; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + } + + public static class Validation { + + private boolean enabled = true; + + private boolean requestOnly = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isRequestOnly() { + return requestOnly; + } + + public void setRequestOnly(boolean requestOnly) { + this.requestOnly = requestOnly; + } + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java new file mode 100644 index 00000000000..3f19d1b58a4 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirRestfulServerCustomizer.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.spring.boot.autoconfigure; + +import ca.uhn.fhir.rest.server.RestfulServer; + +@FunctionalInterface +public interface FhirRestfulServerCustomizer { + + /** + * Customize the server. + * @param server the server to customize + */ + void customize(RestfulServer server); +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..c761b7cf3e0 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java new file mode 100644 index 00000000000..7f9f8fc16ff --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java @@ -0,0 +1,170 @@ +package ca.uhn.fhir.spring.boot.autoconfigure; + +import java.net.URL; +import java.net.URLClassLoader; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; +import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.spring.boot.autoconfigure.FhirAutoConfiguration.FhirJpaServerConfiguration.Dstu3; +import org.assertj.core.util.Arrays; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FhirAutoConfiguration}. + * + * @author Mathieu Ouellet + */ +public class FhirAutoConfigurationTest { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void withFhirContext() throws Exception { + load(); + assertThat(this.context.getBeansOfType(FhirContext.class)).hasSize(1); + } + + @Test + public void withFhirVersion() throws Exception { + load("hapi.fhir.version:DSTU3"); + assertThat(this.context.getBean(FhirContext.class).getVersion()).isEqualTo(FhirVersionEnum.DSTU3.getVersionImplementation()); + } + + @Test + public void withRestfulServer() { + load("hapi.fhir.server.path:/hapi-fhir/*"); + assertThat(this.context.getBeansOfType(ServletRegistrationBean.class)).hasSize(1); + assertThat(this.context.getBeansOfType(RestfulServer.class)).hasSize(1); + assertThat(this.context.getBean(ServletRegistrationBean.class).getUrlMappings()).contains("/hapi-fhir/*"); + } + + @Test + public void withJpaServer() { + load( + Arrays.array( + EmbeddedDataSourceConfiguration.class, + HibernateJpaAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, + FhirAutoConfiguration.class), + "hapi.fhir.version:DSTU3", + "spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles"); + assertThat(this.context.getBeansOfType(DaoConfig.class)).hasSize(1); + assertThat(this.context.getBeansOfType(Dstu3.class)).hasSize(1); + } + + @Test + public void withNoValidation() { + load("hapi.fhir.validation.enabled:false"); + this.thrown.expect(NoSuchBeanDefinitionException.class); + this.context.getBean(RequestValidatingInterceptor.class); + } + + @Test + public void withValidation() { + load(); + assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(1); + } + + @Test + public void withValidations() { + load("hapi.fhir.validation.request-only=false"); + assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(2); + } + + @Test + public void withCustomValidationSchemaLocation() { + load("hapi.fhir.validation.schema-location:custom-schema-location"); + assertThat(this.context.getBeansOfType(IServerInterceptor.class)).hasSize(1); + } + + @Test + public void withApacheHttpClient() { + load(new HidePackagesClassLoader("okhttp3"), "hapi.fhir.server.url:http://localhost:8080"); + assertThat(this.context.getBeansOfType(ApacheRestfulClientFactory.class)).hasSize(1); + assertThat(this.context.getBeansOfType(OkHttpRestfulClientFactory.class)).hasSize(0); + } + + @Test + public void withOkHttpClient() { + load("hapi.fhir.server.url:http://localhost:8080"); + assertThat(this.context.getBeansOfType(OkHttpRestfulClientFactory.class)).hasSize(1); + assertThat(this.context.getBeansOfType(ApacheRestfulClientFactory.class)).hasSize(0); + } + + private void load(String... environment) { + load(new Class[] { FhirAutoConfiguration.class }, null, environment); + } + + private void load(ClassLoader classLoader, String... environment) { + load(new Class[] { FhirAutoConfiguration.class }, classLoader, environment); + } + + private void load(Class[] configs, String... environment) { + load(configs, null, environment); + } + + private void load(Class[] configs, ClassLoader classLoader, String... environment) { + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(applicationContext, environment); + if (classLoader != null) { + applicationContext.setClassLoader(classLoader); + } + if (configs != null) { + applicationContext.register(configs); + } + applicationContext.refresh(); + this.context = applicationContext; + } + + private static final class HidePackagesClassLoader extends URLClassLoader { + + private final String[] hiddenPackages; + + private HidePackagesClassLoader(String... hiddenPackages) { + super(new URL[0], FhirAutoConfigurationTest.class.getClassLoader()); + this.hiddenPackages = hiddenPackages; + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + for (String hiddenPackage : this.hiddenPackages) { + if (name.startsWith(hiddenPackage)) { + throw new ClassNotFoundException(); + } + } + return super.loadClass(name, resolve); + } + + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml new file mode 100644 index 00000000000..f3a8cf9c934 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/application.yml @@ -0,0 +1,13 @@ +spring: + jpa: + hibernate: + ddl-auto: update + properties: + hibernate.jdbc.batch_size: 20 + hibernate.cache.use_query_cache: false + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_structured_entries: false + hibernate.cache.use_minimal_puts: false + hibernate.search.default.directory_provider: filesystem + hibernate.search.default.indexBase: target/lucenefiles + hibernate.search.lucene_version: LUCENE_CURRENT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..ee274734003 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/resources/logback-test.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml new file mode 100644 index 00000000000..963adda0fcb --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-client-apache + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-client + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java new file mode 100644 index 00000000000..e553eebc646 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/java/sample/fhir/client/SampleApacheRestfulClientApplication.java @@ -0,0 +1,37 @@ +package sample.fhir.client; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.dstu3.model.CapabilityStatement; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SampleApacheRestfulClientApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleApacheRestfulClientApplication.class, args); + } + + @Bean + public LoggingInterceptor loggingInterceptor() { + return new LoggingInterceptor(true); + } + + @Bean + public CommandLineRunner runner(final IGenericClient fhirClient) { + return new CommandLineRunner() { + + @Override + public void run(String... args) throws Exception { + fhirClient.capabilities() + .ofType(CapabilityStatement.class) + .execute(); + } + }; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml new file mode 100644 index 00000000000..33eb38f1d70 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/src/main/resources/application.yml @@ -0,0 +1,13 @@ +server: + port: 8888 +hapi: + fhir: + version: dstu3 + server: + url: http://localhost:8080/fhir +management: + security: + enabled: false +logging: + level: + ca.uhn.fhir.jaxrs: debug \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml new file mode 100644 index 00000000000..69cc6e9f6b1 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-client-okhttp + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu3 + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-client-okhttp + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java new file mode 100644 index 00000000000..6286d41b824 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/java/sample/fhir/client/SampleOkHttpRestfulClientApplication.java @@ -0,0 +1,37 @@ +package sample.fhir.client; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.dstu3.model.CapabilityStatement; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SampleOkHttpRestfulClientApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleOkHttpRestfulClientApplication.class, args); + } + + @Bean + public LoggingInterceptor loggingInterceptor() { + return new LoggingInterceptor(true); + } + + @Bean + public CommandLineRunner runner(final IGenericClient fhirClient) { + return new CommandLineRunner() { + + @Override + public void run(String... args) throws Exception { + fhirClient.capabilities() + .ofType(CapabilityStatement.class) + .execute(); + } + }; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml new file mode 100644 index 00000000000..33eb38f1d70 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/src/main/resources/application.yml @@ -0,0 +1,13 @@ +server: + port: 8888 +hapi: + fhir: + version: dstu3 + server: + url: http://localhost:8080/fhir +management: + security: + enabled: false +logging: + level: + ca.uhn.fhir.jaxrs: debug \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml new file mode 100644 index 00000000000..439a5ce9252 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -0,0 +1,67 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-server-jersey + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu3 + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java new file mode 100644 index 00000000000..76b50ec7101 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplication.java @@ -0,0 +1,20 @@ +package sample.fhir.server.jersey; + +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SampleJerseyRestfulServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleJerseyRestfulServerApplication.class, args); + } + + @Bean + public LoggingInterceptor loggingInterceptor() { + return new LoggingInterceptor(); + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java new file mode 100644 index 00000000000..9f12b953a26 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/java/sample/fhir/server/jersey/provider/PatientResourceProvider.java @@ -0,0 +1,73 @@ +package sample.fhir.server.jersey.provider; + +import java.util.concurrent.ConcurrentHashMap; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.dstu3.model.HumanName; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Patient; + +import org.springframework.stereotype.Component; + +@Component +public class PatientResourceProvider extends AbstractJaxRsResourceProvider { + + private static Long counter = 1L; + + private static final ConcurrentHashMap patients = new ConcurrentHashMap<>(); + + static { + patients.put(String.valueOf(counter), createPatient("Van Houte")); + patients.put(String.valueOf(counter), createPatient("Agnew")); + for (int i = 0; i < 20; i++) { + patients.put(String.valueOf(counter), createPatient("Random Patient " + counter)); + } + } + + public PatientResourceProvider(FhirContext fhirContext) { + super(fhirContext); + } + + @Read + public Patient find(@IdParam final IdType theId) { + if (patients.containsKey(theId.getIdPart())) { + return patients.get(theId.getIdPart()); + } else { + throw new ResourceNotFoundException(theId); + } + } + + @Create + public MethodOutcome createPatient(@ResourceParam Patient patient) { + + patient.setId(createId(counter, 1L)); + patients.put(String.valueOf(counter), patient); + + return new MethodOutcome(patient.getIdElement()); + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + private static IdType createId(final Long id, final Long theVersionId) { + return new IdType("Patient", "" + id, "" + theVersionId); + } + + private static Patient createPatient(final String name) { + final Patient patient = new Patient(); + patient.getName().add(new HumanName().setFamily(name)); + patient.setId(createId(counter, 1L)); + counter++; + return patient; + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml new file mode 100644 index 00000000000..325409646b7 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/main/resources/application.yml @@ -0,0 +1,21 @@ +hapi: + fhir: + version: dstu3 + server: + path: /fhir/* + rest: + server-name: hapi-fhir-spring-boot-sample-server-jersey + server-version: 1.0.0 + implementation-description: Spring Boot Jersey Server Sample + default-response-encoding: json + e-tag-support: enabled + default-pretty-print: true + validation: + enabled: true + request-only: true +management: + security: + enabled: false +logging: + level: + ca.uhn.fhir.jaxrs: debug \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java new file mode 100644 index 00000000000..59f41dea74b --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/src/test/java/sample/fhir/server/jersey/SampleJerseyRestfulServerApplicationTest.java @@ -0,0 +1,41 @@ +package sample.fhir.server.jersey; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class SampleJerseyRestfulServerApplicationTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + public void metadata() { + ResponseEntity entity = this.restTemplate.getForEntity( + "/fhir/metadata", + String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("\"status\": \"active\""); + } + + @Test + public void patientResource() { + ResponseEntity entity = this.restTemplate.getForEntity( + "/fhir/Patient/1", + String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("\"family\": \"Van Houte\""); + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml new file mode 100644 index 00000000000..a7ecaf84aa2 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/pom.xml @@ -0,0 +1,75 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-samples + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-sample-server-jpa + + jar + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-jersey + + + org.springframework.boot + spring-boot-starter-data-jpa + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-starter + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + ${project.version} + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java new file mode 100644 index 00000000000..721e4b8d3a1 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplication.java @@ -0,0 +1,12 @@ +package sample.fhir.server.jpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleJpaRestfulServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleJpaRestfulServerApplication.class, args); + } +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml new file mode 100644 index 00000000000..a28ca61340f --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml @@ -0,0 +1,37 @@ +spring: + jpa: + hibernate: + ddl-auto: create-drop + properties: + hibernate.jdbc.batch_size: 20 + hibernate.cache.use_query_cache: false + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_structured_entries: false + hibernate.cache.use_minimal_puts: false + hibernate.search.default.directory_provider: filesystem + hibernate.search.default.indexBase: target/lucenefiles + hibernate.search.lucene_version: LUCENE_CURRENT + h2: + console: + enabled: true +hapi: + fhir: + version: dstu3 + server: + path: /fhir/* + rest: + server-name: hapi-fhir-spring-boot-sample-server-jpa + server-version: 1.0.0 + implementation-description: Spring Boot Jpa Server Sample + default-response-encoding: json + e-tag-support: enabled + default-pretty-print: true + validation: + enabled: true + request-only: true + jpa: + scheduling-disabled: true + subscription-enabled: false +management: + security: + enabled: false diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java new file mode 100644 index 00000000000..bc36ba6eb25 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/test/java/sample/fhir/server/jpa/SampleJpaRestfulServerApplicationTest.java @@ -0,0 +1,41 @@ +package sample.fhir.server.jpa; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class SampleJpaRestfulServerApplicationTest { + + @Autowired + FhirContext fhirContext; + + @LocalServerPort + int port; + + @Test + public void createAndRead() { + IGenericClient client = fhirContext.newRestfulGenericClient("http://localhost:" + port + "/fhir"); + + Patient patient = new Patient(); + patient.addName().setFamily("Test"); + IIdType id = client.create().resource(patient).execute().getId(); + + System.out.println(id); + + Patient result = client.read().resource(Patient.class).withId(id).execute(); + + System.out.println(result); + } + +} diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml new file mode 100644 index 00000000000..29511655859 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-samples + pom + + + hapi-fhir-spring-boot-sample-client-apache + hapi-fhir-spring-boot-sample-client-okhttp + hapi-fhir-spring-boot-sample-server-jersey + hapi-fhir-spring-boot-sample-server-jpa + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml new file mode 100644 index 00000000000..094a37377e3 --- /dev/null +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot + 3.1.0-SNAPSHOT + + + hapi-fhir-spring-boot-starter + + jar + + + + org.springframework.boot + spring-boot-starter + + + ca.uhn.hapi.fhir + hapi-fhir-spring-boot-autoconfigure + ${project.version} + + + + \ No newline at end of file diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml new file mode 100644 index 00000000000..a266e1653ed --- /dev/null +++ b/hapi-fhir-spring-boot/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-fhir + 3.1.0-SNAPSHOT + ../pom.xml + + + hapi-fhir-spring-boot + pom + + + hapi-fhir-spring-boot-autoconfigure + hapi-fhir-spring-boot-starter + hapi-fhir-spring-boot-samples + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index b4c2c1b6367..d3e55664015 100644 --- a/pom.xml +++ b/pom.xml @@ -396,6 +396,7 @@ 2.7.1 4.4.11 4.3.10.RELEASE + 1.5.6.RELEASE 3.0.7.RELEASE @@ -963,6 +964,13 @@ spring-websocket ${spring_version} + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + org.thymeleaf thymeleaf @@ -1875,6 +1883,7 @@ example-projects/hapi-fhir-standalone-overlay-example hapi-fhir-jacoco hapi-fhir-igpacks + hapi-fhir-spring-boot From 65c3a316f21ba17783b6ef1b0ccd9980772fd442 Mon Sep 17 00:00:00 2001 From: Jiajing LIANG Date: Thu, 5 Oct 2017 18:54:42 +0200 Subject: [PATCH 02/24] move out the analysers definitions from entity classes --- .../ca/uhn/fhir/jpa/demo/FhirDbConfig.java | 2 + .../ca/uhn/fhir/jpa/entity/ResourceTable.java | 59 ------------------- .../ca/uhn/fhir/jpa/entity/TermConcept.java | 10 ---- .../fhir/jpa/search/SearchMappingFactory.java | 53 +++++++++++++++++ .../uhn/fhir/jpa/config/TestDstu2Config.java | 1 + .../uhn/fhir/jpa/config/TestDstu3Config.java | 1 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 1 + .../uhn/fhir/jpa/demo/FhirServerConfig.java | 2 + .../fhir/jpa/demo/FhirServerConfigDstu2.java | 2 + .../uhn/fhir/jpa/demo/FhirServerConfig.java | 1 + .../uhn/fhirtest/config/TdlDstu2Config.java | 2 + .../uhn/fhirtest/config/TdlDstu3Config.java | 2 + .../uhn/fhirtest/config/TestDstu2Config.java | 2 + .../uhn/fhirtest/config/TestDstu3Config.java | 2 + .../ca/uhn/fhirtest/config/TestR4Config.java | 2 + 15 files changed, 73 insertions(+), 69 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java index 70751c6f170..e4592a10b8c 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.demo; import java.util.Properties; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,6 +24,7 @@ public class FhirDbConfig { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 4df1d50cfd1..056710bcca0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -26,20 +26,8 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.lucene.analysis.core.LowerCaseFilterFactory; -import org.apache.lucene.analysis.core.StopFilterFactory; -import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory; -import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory; -import org.apache.lucene.analysis.ngram.NGramFilterFactory; -import org.apache.lucene.analysis.pattern.PatternTokenizerFactory; -import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory; -import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory; -import org.apache.lucene.analysis.standard.StandardFilterFactory; -import org.apache.lucene.analysis.standard.StandardTokenizerFactory; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.OptimisticLock; import org.hibernate.search.annotations.*; -import org.hibernate.search.annotations.Parameter; import javax.persistence.*; import javax.persistence.Index; @@ -61,53 +49,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString; @Index(name = "IDX_RES_TYPE", columnList = "RES_TYPE"), @Index(name = "IDX_INDEXSTATUS", columnList = "SP_INDEX_STATUS") }) -@AnalyzerDefs({ - @AnalyzerDef(name = "autocompleteEdgeAnalyzer", - tokenizer = @TokenizerDef(factory = PatternTokenizerFactory.class, params = { - @Parameter(name = "pattern", value = "(.*)"), - @Parameter(name = "group", value = "1") - }), - filters = { - @TokenFilterDef(factory = LowerCaseFilterFactory.class), - @TokenFilterDef(factory = StopFilterFactory.class), - @TokenFilterDef(factory = EdgeNGramFilterFactory.class, params = { - @Parameter(name = "minGramSize", value = "3"), - @Parameter(name = "maxGramSize", value = "50") - }), - }), - @AnalyzerDef(name = "autocompletePhoneticAnalyzer", - tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), - filters = { - @TokenFilterDef(factory = StandardFilterFactory.class), - @TokenFilterDef(factory = StopFilterFactory.class), - @TokenFilterDef(factory = PhoneticFilterFactory.class, params = { - @Parameter(name = "encoder", value = "DoubleMetaphone") - }), - @TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = { - @Parameter(name = "language", value = "English") - }) - }), - @AnalyzerDef(name = "autocompleteNGramAnalyzer", - tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), - filters = { - @TokenFilterDef(factory = WordDelimiterFilterFactory.class), - @TokenFilterDef(factory = LowerCaseFilterFactory.class), - @TokenFilterDef(factory = NGramFilterFactory.class, params = { - @Parameter(name = "minGramSize", value = "3"), - @Parameter(name = "maxGramSize", value = "20") - }), - }), - @AnalyzerDef(name = "standardAnalyzer", - tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), - filters = { - @TokenFilterDef(factory = LowerCaseFilterFactory.class), - }), - @AnalyzerDef(name = "exactAnalyzer", - tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), - filters = { - }) -} -) //@formatter:on public class ResourceTable extends BaseHasResource implements Serializable { static final int RESTYPE_LEN = 30; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 800e71ee10f..0cb567950db 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -52,16 +52,12 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory; import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyzer; -import org.hibernate.search.annotations.AnalyzerDef; -import org.hibernate.search.annotations.AnalyzerDefs; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.Store; -import org.hibernate.search.annotations.TokenizerDef; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; @@ -73,12 +69,6 @@ import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; }, indexes= { @Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList="INDEX_STATUS") }) -@AnalyzerDefs({ - @AnalyzerDef(name = "conceptParentPidsAnalyzer", - tokenizer = @TokenizerDef(factory = WhitespaceTokenizerFactory.class), - filters = { - }) -}) public class TermConcept implements Serializable { private static final int MAX_DESC_LENGTH = 400; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TermConcept.class); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java new file mode 100644 index 00000000000..4fa881573ef --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java @@ -0,0 +1,53 @@ +package ca.uhn.fhir.jpa.search; + +import org.apache.lucene.analysis.core.LowerCaseFilterFactory; +import org.apache.lucene.analysis.core.StopFilterFactory; +import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory; +import org.apache.lucene.analysis.miscellaneous.WordDelimiterFilterFactory; +import org.apache.lucene.analysis.ngram.EdgeNGramFilterFactory; +import org.apache.lucene.analysis.ngram.NGramFilterFactory; +import org.apache.lucene.analysis.pattern.PatternTokenizerFactory; +import org.apache.lucene.analysis.phonetic.PhoneticFilterFactory; +import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory; +import org.apache.lucene.analysis.standard.StandardFilterFactory; +import org.apache.lucene.analysis.standard.StandardTokenizerFactory; +import org.hibernate.search.annotations.Factory; +import org.hibernate.search.cfg.SearchMapping; + +/** + * Factory for defining the analysers. + */ +public class SearchMappingFactory { + @Factory + public SearchMapping getSearchMapping() { + SearchMapping mapping = new SearchMapping(); + + mapping.analyzerDef("autocompleteEdgeAnalyzer", PatternTokenizerFactory.class) + .tokenizerParam("pattern", "(.*)") + .tokenizerParam("group", "1") + .filter(LowerCaseFilterFactory.class) + .filter(StopFilterFactory.class) + .filter(EdgeNGramFilterFactory.class) + .param("minGramSize", "3") + .param("maxGramSize", "50") + .analyzerDef("autocompletePhoneticAnalyzer", StandardTokenizerFactory.class) + .filter(StandardFilterFactory.class) + .filter(StopFilterFactory.class) + .filter(PhoneticFilterFactory.class) + .param("encoder", "DoubleMetaphone") + .filter(SnowballPorterFilterFactory.class) + .param("language", "English") + .analyzerDef("autocompleteNGramAnalyzer", StandardTokenizerFactory.class) + .filter(WordDelimiterFilterFactory.class) + .filter(LowerCaseFilterFactory.class) + .filter(NGramFilterFactory.class) + .param("minGramSize", "3") + .param("maxGramSize", "20") + .analyzerDef("standardAnalyzer", StandardTokenizerFactory.class) + .filter(LowerCaseFilterFactory.class) + .analyzerDef("exactAnalyzer", StandardTokenizerFactory.class) + .analyzerDef("conceptParentPidsAnalyzer", WhitespaceTokenizerFactory.class); + + return mapping; + } +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index d2852edca9b..e3c4d83b218 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -60,6 +60,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); return extraProperties; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index c827bb7d27e..bd7f2fa3658 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -131,6 +131,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.autoregister_listeners", "true"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 82b78e27799..c1b2a29f0bb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -125,6 +125,7 @@ public class TestR4Config extends BaseJavaConfigR4 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); + extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.autoregister_listeners", "true"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 90f791c0919..0f1063e93e9 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -5,6 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.jpa.HibernatePersistenceProvider; @@ -80,6 +81,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java index 0d45ad6b25d..75cc4d7c303 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.demo; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -80,6 +81,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 4a19ef39012..ae55d7a1ed3 100644 --- a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -84,6 +84,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java index 47380b48473..11c7d09bb78 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java @@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -110,6 +111,7 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java index 052d974092d..3e76e5e806d 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java @@ -5,6 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; @@ -93,6 +94,7 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index 7b6dabda413..a57d039654f 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -2,6 +2,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; @@ -115,6 +116,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index 11189d2f7bc..3f48eb9249b 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -5,6 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; @@ -112,6 +113,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index 609b537f500..6d2ffbff8df 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -5,6 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import ca.uhn.fhir.jpa.search.SearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; @@ -108,6 +109,7 @@ public class TestR4Config extends BaseJavaConfigR4 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); + extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); From 86fb1de3ae3f14a13daa3a729e3261e397954835 Mon Sep 17 00:00:00 2001 From: Jiajing LIANG Date: Thu, 5 Oct 2017 21:14:59 +0200 Subject: [PATCH 03/24] rename file --- .../src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java | 4 ++-- ...hMappingFactory.java => LuceneSearchMappingFactory.java} | 2 +- .../test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java | 4 ++-- .../test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java | 3 ++- .../src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java | 3 ++- .../main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java | 4 ++-- .../java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java | 6 ++---- .../main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java | 4 ++-- .../main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java | 5 ++--- .../main/java/ca/uhn/fhirtest/config/TestDstu2Config.java | 4 ++-- .../main/java/ca/uhn/fhirtest/config/TestDstu3Config.java | 4 ++-- .../src/main/java/ca/uhn/fhirtest/config/TestR4Config.java | 5 ++--- 12 files changed, 23 insertions(+), 25 deletions(-) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/{SearchMappingFactory.java => LuceneSearchMappingFactory.java} (98%) diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java index e4592a10b8c..e7f00c4d037 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/FhirDbConfig.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.demo; import java.util.Properties; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,7 +24,7 @@ public class FhirDbConfig { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java similarity index 98% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java index 4fa881573ef..fdf74d57ba3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchMappingFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java @@ -17,7 +17,7 @@ import org.hibernate.search.cfg.SearchMapping; /** * Factory for defining the analysers. */ -public class SearchMappingFactory { +public class LuceneSearchMappingFactory { @Factory public SearchMapping getSearchMapping() { SearchMapping mapping = new SearchMapping(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index e3c4d83b218..49c9107ce28 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -1,7 +1,7 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import org.apache.commons.dbcp2.BasicDataSource; @@ -60,7 +60,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); - extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); return extraProperties; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index bd7f2fa3658..15d1d45a06e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import org.apache.commons.dbcp2.BasicDataSource; import org.hibernate.jpa.HibernatePersistenceProvider; @@ -131,7 +132,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); - extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.autoregister_listeners", "true"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index c1b2a29f0bb..f0ee6652136 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; @@ -125,7 +126,7 @@ public class TestR4Config extends BaseJavaConfigR4 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); - extraProperties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.SearchMappingFactory"); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.autoregister_listeners", "true"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java index 0f1063e93e9..69569f72085 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfig.java @@ -5,7 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.jpa.HibernatePersistenceProvider; @@ -81,7 +81,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java index 75cc4d7c303..9a6b52e0a2d 100644 --- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java +++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/FhirServerConfigDstu2.java @@ -1,11 +1,9 @@ package ca.uhn.fhir.jpa.demo; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; -import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; @@ -81,7 +79,7 @@ public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java index 11c7d09bb78..57e971ef408 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu2Config.java @@ -2,7 +2,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -111,7 +111,7 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider" ,"filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java index 3e76e5e806d..0f6836d98e5 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TdlDstu3Config.java @@ -5,7 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; @@ -14,7 +14,6 @@ import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @@ -94,7 +93,7 @@ public class TdlDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index a57d039654f..ec38c09ad66 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -2,7 +2,7 @@ package ca.uhn.fhirtest.config; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; @@ -116,7 +116,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index 3f48eb9249b..cf15684ddd6 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -5,7 +5,7 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; @@ -113,7 +113,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java index 6d2ffbff8df..91fa6e45a67 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java @@ -5,12 +5,11 @@ import java.util.Properties; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import ca.uhn.fhir.jpa.search.SearchMappingFactory; +import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.lang3.time.DateUtils; import org.hibernate.dialect.DerbyTenSevenDialect; import org.hibernate.dialect.PostgreSQL94Dialect; -import org.hibernate.dialect.PostgreSQL95Dialect; import org.hibernate.jpa.HibernatePersistenceProvider; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Value; @@ -109,7 +108,7 @@ public class TestR4Config extends BaseJavaConfigR4 { extraProperties.put("hibernate.cache.use_second_level_cache", "false"); extraProperties.put("hibernate.cache.use_structured_entries", "false"); extraProperties.put("hibernate.cache.use_minimal_puts", "false"); - extraProperties.put("hibernate.search.model_mapping", SearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "filesystem"); extraProperties.put("hibernate.search.default.indexBase", myFhirLuceneLocation); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); From 462557b110a3d87e14c924c121feba8c60dc6295 Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Tue, 10 Oct 2017 19:55:22 -0500 Subject: [PATCH 04/24] Added a client interceptor called AdditionalRequestHeadersInterceptor to add arbitrary HTTP headers to the client request --- .../AdditionalRequestHeadersInterceptor.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java new file mode 100644 index 00000000000..80e9459a68b --- /dev/null +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java @@ -0,0 +1,96 @@ +package ca.uhn.fhir.rest.client.interceptor; + +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * This interceptor adds arbitrary header values to requests made by the client. + */ +public class AdditionalRequestHeadersInterceptor implements IClientInterceptor { + private final Map> additionalHttpHeaders = new HashMap<>(); + + public AdditionalRequestHeadersInterceptor() { + this(new HashMap>()); + } + + public AdditionalRequestHeadersInterceptor(Map> additionalHttpHeaders) { + super(); + if (additionalHttpHeaders != null) { + this.additionalHttpHeaders.putAll(additionalHttpHeaders); + } + } + + /** + * Adds the given header value. + * Note that {@code headerName} and {@code headerValue} cannot be null. + * @param headerName the name of the header + * @param headerValue the value to add for the header + * @throws NullPointerException if either parameter is {@code null} + */ + public void addHeaderValue(String headerName, String headerValue) throws NullPointerException { + Objects.requireNonNull(headerName, "headerName cannot be null"); + Objects.requireNonNull(headerValue, "headerValue cannot be null"); + + getHeaderValues(headerName).add(headerValue); + } + + /** + * Adds the list of header values for the given header. + * Note that {@code headerName} and {@code headerValues} cannot be null. + * @param headerName the name of the header + * @param headerValues the list of values to add for the header + * @throws NullPointerException if either parameter is {@code null} + */ + public void addAllHeaderValues(String headerName, List headerValues) throws NullPointerException { + Objects.requireNonNull(headerName, "headerName cannot be null"); + Objects.requireNonNull(headerValues, "headerValues cannot be null"); + + getHeaderValues(headerName).addAll(headerValues); + } + + /** + * Gets the header values list for a given header. + * If the header doesn't have any values, an empty list will be returned. + * @param headerName the name of the header + * @return the list of values for the header + */ + private List getHeaderValues(String headerName) { + if (additionalHttpHeaders.get(headerName) == null) { + additionalHttpHeaders.put(headerName, new ArrayList()); + } + return additionalHttpHeaders.get(headerName); + } + + /** + * Adds the additional header values to the HTTP request. + * @param theRequest the HTTP request + */ + @Override + public void interceptRequest(IHttpRequest theRequest) { + for (Map.Entry> header : additionalHttpHeaders.entrySet()) { + for (String headerValue : header.getValue()) { + if (headerValue != null) { + theRequest.addHeader(header.getKey(), headerValue); + } + } + } + } + + /** + * Does nothing since this interceptor is not concerned with the response. + * @param theResponse the HTTP response + * @throws IOException + */ + @Override + public void interceptResponse(IHttpResponse theResponse) throws IOException { + // Do nothing. This interceptor is not concerned with the response. + } +} From 7076b55f8cb9aa09f8faab3587b92b860729f149 Mon Sep 17 00:00:00 2001 From: Clayton Bodendein Date: Mon, 30 Oct 2017 12:01:56 -0500 Subject: [PATCH 05/24] Removed the throwing of NullPointerException from AdditionalRequestHeadersInterceptor since that isn't common due to NullPointerException being a runtime exception --- .../interceptor/AdditionalRequestHeadersInterceptor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java index 80e9459a68b..840a47f4db4 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/AdditionalRequestHeadersInterceptor.java @@ -35,7 +35,7 @@ public class AdditionalRequestHeadersInterceptor implements IClientInterceptor { * @param headerValue the value to add for the header * @throws NullPointerException if either parameter is {@code null} */ - public void addHeaderValue(String headerName, String headerValue) throws NullPointerException { + public void addHeaderValue(String headerName, String headerValue) { Objects.requireNonNull(headerName, "headerName cannot be null"); Objects.requireNonNull(headerValue, "headerValue cannot be null"); @@ -49,7 +49,7 @@ public class AdditionalRequestHeadersInterceptor implements IClientInterceptor { * @param headerValues the list of values to add for the header * @throws NullPointerException if either parameter is {@code null} */ - public void addAllHeaderValues(String headerName, List headerValues) throws NullPointerException { + public void addAllHeaderValues(String headerName, List headerValues) { Objects.requireNonNull(headerName, "headerName cannot be null"); Objects.requireNonNull(headerValues, "headerValues cannot be null"); From 59f4177a59ade97e03cf5ebb4f5f7865c9e76ed6 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 6 Nov 2017 19:49:50 -0500 Subject: [PATCH 06/24] Fix #750 - Elements are not preserved in page requests --- .../java/ca/uhn/fhir/parser/BaseParser.java | 23 +- .../main/java/ca/uhn/fhir/parser/IParser.java | 16 + .../java/ca/uhn/fhir/util/BinaryUtil.java | 9 +- .../fhir/rest/server/RestfulServerUtils.java | 54 +- .../BaseResourceReturningMethodBinding.java | 2 +- .../fhir/parser/JsonParserDstu2_1Test.java | 2 +- .../uhn/fhir/parser/XmlParserDstu3Test.java | 1787 +++++++++-------- .../ca/uhn/fhir/rest/server/SearchR4Test.java | 239 ++- src/changes/changes.xml | 15 + 9 files changed, 1148 insertions(+), 999 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 6e5b8290027..9dfcdb7c7da 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -43,7 +43,7 @@ public abstract class BaseParser implements IParser { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class); private ContainedResources myContainedResources; - + private boolean myEncodeElementsAppliesToChildResourcesOnly; private FhirContext myContext; private Set myDontEncodeElements; private boolean myDontEncodeElementsIncludesStars; @@ -556,6 +556,16 @@ public abstract class BaseParser implements IParser { && theIncludedResource == false; } + @Override + public boolean isEncodeElementsAppliesToChildResourcesOnly() { + return myEncodeElementsAppliesToChildResourcesOnly; + } + + @Override + public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) { + myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly; + } + @Override public boolean isOmitResourceId() { return myOmitResourceId; @@ -1039,7 +1049,13 @@ public abstract class BaseParser implements IParser { } private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { - return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true); + Set encodeElements = myEncodeElements; + if (encodeElements != null && encodeElements.isEmpty() == false) { + if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) { + encodeElements = null; + } + } + return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true); } private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) { @@ -1058,6 +1074,9 @@ public abstract class BaseParser implements IParser { } else { thePathBuilder.append(myResDef.getName()); } + if (theElements == null) { + return true; + } if (theElements.contains(thePathBuilder.toString())) { return true; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index d12e681231d..54de21c62b4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -206,6 +206,22 @@ public interface IParser { */ void setEncodeElements(Set theEncodeElements); + /** + * If set to true (default is false), the values supplied + * to {@link #setEncodeElements(Set)} will not be applied to the root + * resource (typically a Bundle), but will be applied to any sub-resources + * contained within it (i.e. search result resources in that bundle) + */ + void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly); + + /** + * If set to true (default is false), the values supplied + * to {@link #setEncodeElements(Set)} will not be applied to the root + * resource (typically a Bundle), but will be applied to any sub-resources + * contained within it (i.e. search result resources in that bundle) + */ + boolean isEncodeElementsAppliesToChildResourcesOnly(); + /** * If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any * resource types not specified here will be encoded completely, with no elements excluded. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java index b25dcc53f48..b6c4411c8f3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BinaryUtil.java @@ -19,11 +19,12 @@ public class BinaryUtil { public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) { RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary"); BaseRuntimeChildDefinition child = def.getChildByName("securityContext"); - - List values = child.getAccessor().getValues(theBinary); IBaseReference retVal = null; - if (values.size() > 0) { - retVal = (IBaseReference) values.get(0); + if (child != null) { + List values = child.getAccessor().getValues(theBinary); + if (values.size() > 0) { + retVal = (IBaseReference) values.get(0); + } } return retVal; } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 4f3d920e8a9..0dfb36051e9 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -86,10 +86,41 @@ public class RestfulServerUtils { } } if (elements != null && elements.size() > 0) { - Set newElements = new HashSet(); + Set newElements = new HashSet<>(); for (String next : elements) { newElements.add("*." + next); } + + /* + * We try to be smart about what the user is asking for + * when they include an _elements parameter. If we're responding + * to something that returns a Bundle (e.g. a search) we assume + * the elements don't apply to the Bundle itself, unless + * the client has explicitly scoped the Bundle + * (i.e. with Bundle.total or something like that) + */ + switch (theRequestDetails.getRestOperationType()) { + case SEARCH_SYSTEM: + case SEARCH_TYPE: + case HISTORY_SYSTEM: + case HISTORY_TYPE: + case HISTORY_INSTANCE: + case GET_PAGE: + boolean haveExplicitBundleElement = false; + for (String next : newElements) { + if (next.startsWith("Bundle.")) { + haveExplicitBundleElement = true; + break; + } + } + if (!haveExplicitBundleElement) { + parser.setEncodeElementsAppliesToChildResourcesOnly(true); + } + break; + default: + break; + } + parser.setEncodeElements(newElements); parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo); } @@ -147,6 +178,19 @@ public class RestfulServerUtils { b.append(theBundleType.getCode()); } + String paramName = Constants.PARAM_ELEMENTS; + String[] params = theRequestParameters.get(paramName); + if (params != null) { + for (String nextValue : params) { + if (isNotBlank(nextValue)) { + b.append('&'); + b.append(paramName); + b.append('='); + b.append(UrlUtil.escape(nextValue)); + } + } + } + return b.toString(); } catch (UnsupportedEncodingException e) { throw new Error("UTF-8 not supported", e);// should not happen @@ -587,9 +631,11 @@ public class RestfulServerUtils { response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); IBaseReference securityContext = BinaryUtil.getSecurityContext(theServer.getFhirContext(), bin); - String securityContextRef = securityContext.getReferenceElement().getValue(); - if (isNotBlank(securityContextRef)) { - response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef); + if (securityContext != null) { + String securityContextRef = securityContext.getReferenceElement().getValue(); + if (isNotBlank(securityContextRef)) { + response.addHeader(Constants.HEADER_X_SECURITY_CONTEXT, securityContextRef); + } } return response.sendAttachmentResponse(bin, theStausCode, contentType); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java index 894e7285e95..60820b85046 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java @@ -332,7 +332,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi } bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished()); - bundleFactory.addResourcesToBundle(new ArrayList(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes); + bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes); if (theServer.getPagingProvider() != null) { int limit; diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java index c4702d6d789..131d79141be 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2_1Test.java @@ -1499,7 +1499,7 @@ public class JsonParserDstu2_1Test { String val = ourCtx.newJsonParser().encodeResourceToString(patient); - String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}"; + String expected = "{\"resourceType\":\"Binary\",\"id\":\"11\",\"meta\":{\"versionId\":\"22\"},\"contentType\":\"foo\",\"content\":\"AQIDBA==\"}"; ourLog.info("Expected: {}", expected); ourLog.info("Actual : {}", val); assertEquals(expected, val); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index fe8a29f4f42..8cd9f644d03 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -1,41 +1,26 @@ package ca.uhn.fhir.parser; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; -import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.IOException; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; +import ca.uhn.fhir.parser.FooMessageHeaderWithExplicitField.FooMessageSourceComponent; +import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation; +import ca.uhn.fhir.parser.PatientWithCustomCompositeExtension.FooParentExtension; +import ca.uhn.fhir.util.TestUtil; +import com.google.common.collect.Sets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.hamcrest.collection.IsEmptyCollection; import org.hamcrest.core.StringContains; import org.hamcrest.text.StringContainsInOrder; -import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Address.AddressUse; import org.hl7.fhir.dstu3.model.Address.AddressUseEnumFactory; -import org.hl7.fhir.dstu3.model.Bundle.*; +import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.dstu3.model.DiagnosticReport.DiagnosticReportStatus; import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent; @@ -52,27 +37,27 @@ import org.junit.*; import org.mockito.ArgumentCaptor; import org.xmlunit.builder.DiffBuilder; import org.xmlunit.builder.Input; -import org.xmlunit.diff.*; +import org.xmlunit.diff.ComparisonControllers; +import org.xmlunit.diff.DefaultNodeMatcher; +import org.xmlunit.diff.Diff; +import org.xmlunit.diff.ElementSelectors; -import com.google.common.collect.Sets; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.parser.FooMessageHeaderWithExplicitField.FooMessageSourceComponent; -import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation; -import ca.uhn.fhir.parser.PatientWithCustomCompositeExtension.FooParentExtension; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.validation.IValidationContext; -import ca.uhn.fhir.validation.SingleValidationMessage; -import ca.uhn.fhir.validation.ValidationContext; -import ca.uhn.fhir.validation.ValidationResult; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; public class XmlParserDstu3Test { - private static FhirContext ourCtx = FhirContext.forDstu3(); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserDstu3Test.class); + private static FhirContext ourCtx = FhirContext.forDstu3(); @After public void after() { @@ -83,21 +68,24 @@ public class XmlParserDstu3Test { } @Test - public void testEncodeBinaryWithSecurityContext() { - Binary bin = new Binary(); - bin.setContentType("text/plain"); - bin.setContent("Now is the time".getBytes()); - Reference securityContext = new Reference(); - securityContext.setReference("DiagnosticReport/1"); - bin.setSecurityContext(securityContext); - String encoded = ourCtx.newXmlParser().encodeResourceToString(bin); - ourLog.info(encoded); - assertThat(encoded, containsString("Binary")); - assertThat(encoded, containsString("")); - assertThat(encoded, containsString("")); - assertThat(encoded, containsString("")); - } + public void testBaseUrlFooResourceCorrectlySerializedInExtensionValueReference() { + String refVal = "http://my.org/FooBar"; + Patient fhirPat = new Patient(); + fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); + + IParser parser = ourCtx.newXmlParser(); + + String output = parser.encodeResourceToString(fhirPat); + System.out.println("output: " + output); + + // Deserialize then check that valueReference value is still correct + fhirPat = parser.parseResource(Patient.class, output); + + List extlst = fhirPat.getExtensionsByUrl("x1"); + Assert.assertEquals(1, extlst.size()); + Assert.assertEquals(refVal, ((Reference) extlst.get(0).getValue()).getReference()); + } /** * See #544 @@ -131,29 +119,29 @@ public class XmlParserDstu3Test { public void testBundleWithBinary() { String bundle = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; Bundle b = ourCtx.newXmlParser().parseResource(Bundle.class, bundle); assertEquals(1, b.getEntry().size()); Binary bin = (Binary) b.getEntry().get(0).getResource(); - assertArrayEquals(new byte[] { 1, 2, 3, 4 }, bin.getContent()); + assertArrayEquals(new byte[]{1, 2, 3, 4}, bin.getContent()); } @@ -298,10 +286,10 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "")); + "", + "", + "", + "")); bundle = ourCtx.newXmlParser().parseResource(Bundle.class, encoded); pt = (Patient) bundle.getEntry().get(0).getResource(); @@ -418,24 +406,24 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); /* * Now PARSE! @@ -469,18 +457,18 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); /* * Now PARSE! @@ -583,10 +571,10 @@ public class XmlParserDstu3Test { assertThat(enc, containsString("")); assertThat(enc, containsString("")); assertThat(enc, containsString( - "")); + "")); assertThat(enc, containsString("")); assertThat(enc, containsString( - "")); + "")); /* * Now parse this back @@ -640,15 +628,15 @@ public class XmlParserDstu3Test { ourLog.info(out); assertThat(out, stringContainsInOrder("", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "")); patient = ourCtx.newXmlParser().parseResource(Patient.class, out); assertEquals("http://hl7.org/fhir/v2/0203", patient.getIdentifier().get(0).getType().getCoding().get(0).getSystem()); @@ -688,26 +676,26 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, enc); List gotLabels = parsed.getMeta().getProfile(); @@ -748,24 +736,24 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, enc); assertThat(parsed.getMeta().getProfile(), empty()); @@ -879,24 +867,24 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder("", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, enc); List gotLabels = parsed.getMeta().getSecurity(); @@ -969,7 +957,7 @@ public class XmlParserDstu3Test { @Test public void testEncodeBinaryWithNoContentType() { Binary b = new Binary(); - b.setContent(new byte[] { 1, 2, 3, 4 }); + b.setContent(new byte[]{1, 2, 3, 4}); String output = ourCtx.newXmlParser().encodeResourceToString(b); ourLog.info(output); @@ -977,6 +965,22 @@ public class XmlParserDstu3Test { assertEquals("", output); } + @Test + public void testEncodeBinaryWithSecurityContext() { + Binary bin = new Binary(); + bin.setContentType("text/plain"); + bin.setContent("Now is the time".getBytes()); + Reference securityContext = new Reference(); + securityContext.setReference("DiagnosticReport/1"); + bin.setSecurityContext(securityContext); + String encoded = ourCtx.newXmlParser().encodeResourceToString(bin); + ourLog.info(encoded); + assertThat(encoded, containsString("Binary")); + assertThat(encoded, containsString("")); + assertThat(encoded, containsString("")); + assertThat(encoded, containsString("")); + } + @Test public void testEncodeBundleWithContained() { DiagnosticReport rpt = new DiagnosticReport(); @@ -1037,19 +1041,19 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); } @@ -1081,9 +1085,9 @@ public class XmlParserDstu3Test { // @formatter:on assertThat(encoded, - stringContainsInOrder("", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "")); + stringContainsInOrder("", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "")); } @@ -1114,9 +1118,9 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, - stringContainsInOrder("", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "")); + stringContainsInOrder("", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "")); } @@ -1147,9 +1151,9 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, - stringContainsInOrder("", "", "", "", "", "", - "", "", "", "", "", "", "", "", - "", "", "")); + stringContainsInOrder("", "", "", "", "", "", + "", "", "", "", "", "", "", "", + "", "", "")); } @@ -1200,9 +1204,9 @@ public class XmlParserDstu3Test { GuidanceResponse.GuidanceResponseStatus status = GuidanceResponse.GuidanceResponseStatus.SUCCESS; GuidanceResponse gr = new GuidanceResponse() - .setRequestId("123") - .setModule(new Reference("Evaluate Operation")) - .setStatus(status); + .setRequestId("123") + .setModule(new Reference("Evaluate Operation")) + .setStatus(status); gr.setReason(new Reference(pr)); gr.getContained().add(p); @@ -1250,9 +1254,9 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - "A P TAG

", - "

line1\nline2\nline3  BOLD
")); + "A P TAG

", + "

line1\nline2\nline3  BOLD
")); } @@ -1266,9 +1270,9 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - " ", - " line1\nline2\nline3 BOLD")); + " ", + " line1\nline2\nline3 BOLD")); } @@ -1327,9 +1331,9 @@ public class XmlParserDstu3Test { Patient p = new Patient(); p.setId("Patient/B"); p - .addExtension() - .setUrl("http://foo") - .setValue(new Reference("Practitioner/A")); + .addExtension() + .setUrl("http://foo") + .setValue(new Reference("Practitioner/A")); IParser parser = ourCtx.newXmlParser().setPrettyPrint(true); parser.setDontEncodeElements(new HashSet(Arrays.asList("*.id", "*.meta"))); @@ -1356,14 +1360,14 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "")); assertThat(output, not(stringContainsInOrder( - ""))); + ""))); obs = parser.parseResource(Observation.class, output); assertEquals(1, obs.getExtension().size()); @@ -1390,15 +1394,15 @@ public class XmlParserDstu3Test { ourLog.info(output); assertThat(output, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "")); assertThat(output, not(stringContainsInOrder( - ""))); + ""))); obs = parser.parseResource(Observation.class, output); assertEquals(1, obs.getExtension().size()); @@ -1427,22 +1431,22 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); } @@ -1514,7 +1518,7 @@ public class XmlParserDstu3Test { assertThat(encoded, containsString("", "", - "", "")); + "", "")); assertThat(encoded, not(containsString("text"))); assertThat(encoded, not(containsString("THE DIV"))); assertThat(encoded, containsString("family")); @@ -1661,7 +1665,7 @@ public class XmlParserDstu3Test { assertThat(encoded, containsString("", "", - "", "")); + "", "")); assertThat(encoded, not(containsString("THE DIV"))); assertThat(encoded, containsString("family")); assertThat(encoded, not(containsString("maritalStatus"))); @@ -1683,7 +1687,7 @@ public class XmlParserDstu3Test { assertThat(encoded, containsString("", "", "", "")); assertThat(encoded, stringContainsInOrder("", "", - "", "")); + "", "")); assertThat(encoded, not(containsString("THE DIV"))); assertThat(encoded, containsString("family")); assertThat(encoded, not(containsString("maritalStatus"))); @@ -1764,29 +1768,29 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "")); assertThat(encoded, not(containsString("#1002"))); } @@ -1872,7 +1876,7 @@ public class XmlParserDstu3Test { { IParser p = ourCtx.newXmlParser(); - p.setEncodeElements(new HashSet(Arrays.asList("Patient.name", "Bundle.entry"))); + p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name", "Bundle.entry"))); p.setPrettyPrint(true); String out = p.encodeResourceToString(bundle); ourLog.info(out); @@ -1908,6 +1912,31 @@ public class XmlParserDstu3Test { } + @Test + public void testEncodeWithEncodeElementsAppliesToChildResourcesOnly() throws Exception { + Patient patient = new Patient(); + patient.getMeta().addProfile("http://profile"); + patient.addName().setFamily("FAMILY"); + patient.addAddress().addLine("LINE1"); + + Bundle bundle = new Bundle(); + bundle.setTotal(100); + bundle.addEntry().setResource(patient); + + { + IParser p = ourCtx.newXmlParser(); + p.setEncodeElements(new HashSet<>(Arrays.asList("Patient.name"))); + p.setEncodeElementsAppliesToChildResourcesOnly(true); + p.setPrettyPrint(true); + String out = p.encodeResourceToString(bundle); + ourLog.info(out); + assertThat(out, containsString("total")); + assertThat(out, containsString("Patient")); + assertThat(out, containsString("name")); + assertThat(out, not(containsString("address"))); + } + } + @Test public void testEncodeWithNarrative() { Patient p = new Patient(); @@ -1986,10 +2015,10 @@ public class XmlParserDstu3Test { String enc = ourCtx.newXmlParser().encodeResourceToString(patient); assertThat(enc, containsString("")); assertThat(enc, containsString( - "")); + "")); assertThat(enc, containsString("")); assertThat(enc, containsString( - "")); + "")); } @Test @@ -2119,34 +2148,34 @@ public class XmlParserDstu3Test { @Test public void testParseAndEncodeComments() { String input = "\n" + - " " + - " \n" + - " \n" + - " \n" + - "
\n" + - "\n" + - "

Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321

\n" + - "\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " " + - " " + - "
"; + " " + + " \n" + + " \n" + + " \n" + + "
\n" + + "\n" + + "

Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321

\n" + + "\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " " + + "
"; Patient res = ourCtx.newXmlParser().parseResource(Patient.class, input); res.getFormatCommentsPre(); @@ -2163,54 +2192,54 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "\"identifier\": [", - "{", - "\"fhir_comments\":", - "[", - "\"identifier comment 1\"", - ",", - "\"identifier comment 2\"", - "]", - "\"use\": \"usual\",", - "\"_use\": {", - "\"fhir_comments\":", - "[", - "\"use comment 1\"", - ",", - "\"use comment 2\"", - "]", - "},", - "\"type\"")); + "\"identifier\": [", + "{", + "\"fhir_comments\":", + "[", + "\"identifier comment 1\"", + ",", + "\"identifier comment 2\"", + "]", + "\"use\": \"usual\",", + "\"_use\": {", + "\"fhir_comments\":", + "[", + "\"use comment 1\"", + ",", + "\"use comment 2\"", + "]", + "},", + "\"type\"")); encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res); ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "", - "
", - "

Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321

", - "
", - "
", - " \n", - " ", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "
")); + "", + "", + "", + "", + "
", + "

Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321

", + "
", + "
", + " \n", + " ", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "
")); } @@ -2218,84 +2247,84 @@ public class XmlParserDstu3Test { public void testParseAndEncodeCommentsOnExtensions() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
"; Patient pat = ourCtx.newXmlParser().parseResource(Patient.class, input); String output = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pat); ourLog.info(output); assertThat(output, stringContainsInOrder( - "", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - " ", - "")); + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "")); output = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(pat); ourLog.info(output); assertThat(output, stringContainsInOrder( - "{", - " \"resourceType\": \"Patient\",", - " \"id\": \"someid\",", - " \"_id\": {", - " \"fhir_comments\": [", - " \" comment 1 \"", - " ]", - " },", - " \"extension\": [", - " {", - " \"fhir_comments\": [", - " \" comment 2 \",", - " \" comment 7 \"", - " ],", - " \"url\": \"urn:patientext:att\",", - " \"valueAttachment\": {", - " \"fhir_comments\": [", - " \" comment 3 \",", - " \" comment 6 \"", - " ],", - " \"contentType\": \"aaaa\",", - " \"_contentType\": {", - " \"fhir_comments\": [", - " \" comment 4 \"", - " ]", - " },", - " \"data\": \"AAAA\",", - " \"_data\": {", - " \"fhir_comments\": [", - " \" comment 5 \"", - " ]", - " }", - " }", - " }", - " ]", - "}")); + "{", + " \"resourceType\": \"Patient\",", + " \"id\": \"someid\",", + " \"_id\": {", + " \"fhir_comments\": [", + " \" comment 1 \"", + " ]", + " },", + " \"extension\": [", + " {", + " \"fhir_comments\": [", + " \" comment 2 \",", + " \" comment 7 \"", + " ],", + " \"url\": \"urn:patientext:att\",", + " \"valueAttachment\": {", + " \"fhir_comments\": [", + " \" comment 3 \",", + " \" comment 6 \"", + " ],", + " \"contentType\": \"aaaa\",", + " \"_contentType\": {", + " \"fhir_comments\": [", + " \" comment 4 \"", + " ]", + " },", + " \"data\": \"AAAA\",", + " \"_data\": {", + " \"fhir_comments\": [", + " \" comment 5 \"", + " ]", + " }", + " }", + " }", + " ]", + "}")); } @@ -2303,203 +2332,203 @@ public class XmlParserDstu3Test { public void testParseAndEncodeExtensionOnReference() { String input = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; DataElement de = ourCtx.newXmlParser().parseResource(DataElement.class, input); String output = ourCtx.newXmlParser().encodeResourceToString(de).replace(" xmlns=\"http://hl7.org/fhir\"", ""); @@ -2534,14 +2563,14 @@ public class XmlParserDstu3Test { public void testParseAndEncodeNestedExtensions() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
"; Patient p = ourCtx.newXmlParser().parseResource(Patient.class, input); DateType bd = p.getBirthDateElement(); @@ -2561,15 +2590,15 @@ public class XmlParserDstu3Test { ourLog.info(encoded); assertThat(encoded, stringContainsInOrder( - "", - "", - "", - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "", + "", + "", + "")); } @@ -2577,16 +2606,16 @@ public class XmlParserDstu3Test { public void testParseBundleNewWithPlaceholderIds() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input); assertEquals("urn:oid:0.1.2.3", parsed.getEntry().get(0).getResource().getIdElement().getValue()); @@ -2597,16 +2626,16 @@ public class XmlParserDstu3Test { public void testParseBundleNewWithPlaceholderIdsInBase1() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input); assertEquals("urn:oid:0.1.2.3", parsed.getEntry().get(0).getResource().getIdElement().getValue()); @@ -2616,31 +2645,31 @@ public class XmlParserDstu3Test { public void testParseBundleNewWithPlaceholderIdsInBase2() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; Bundle parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input); assertEquals("urn:uuid:0.1.2.3", parsed.getEntry().get(0).getResource().getIdElement().getValue()); input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; parsed = ourCtx.newXmlParser().parseResource(Bundle.class, input); assertEquals("urn:uuid:0.1.2.3", parsed.getEntry().get(0).getResource().getIdElement().getValue()); @@ -2651,29 +2680,29 @@ public class XmlParserDstu3Test { public void testParseBundleOldStyleWithUnknownLinks() throws Exception { String bundle = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; Bundle b = (Bundle) ourCtx.newXmlParser().parseResource(bundle); assertEquals(1, b.getEntry().size()); @@ -2684,16 +2713,16 @@ public class XmlParserDstu3Test { public void testParseBundleOldWithPlaceholderIds() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; Bundle parsed = (Bundle) ourCtx.newXmlParser().parseResource(input); assertEquals("urn:oid:0.1.2.3", parsed.getEntry().get(0).getResource().getId()); @@ -2727,10 +2756,10 @@ public class XmlParserDstu3Test { public void testParseBundleWithResourceId() { String input = "" - + "" - + "" - + "" - + "\n"; + + "" + + "" + + "" + + "\n"; Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, input); assertEquals("http://localhost:58402/fhir/context/Patient/1/_history/3", bundle.getEntry().get(0).getResource().getIdElement().getValue()); @@ -2759,12 +2788,12 @@ public class XmlParserDstu3Test { ourLog.info(enc); assertThat(enc, stringContainsInOrder( - "", - "", - "", - "", - "", - "")); + "", + "", + "", + "", + "", + "")); o = parser.parseResource(Observation.class, enc); assertEquals("obs text", o.getCode().getText()); @@ -2779,7 +2808,7 @@ public class XmlParserDstu3Test { */ @Test public void testParseContainedBinaryResource() { - byte[] bin = new byte[] { 0, 1, 2, 3, 4 }; + byte[] bin = new byte[]{0, 1, 2, 3, 4}; final Binary binary = new Binary(); binary.setContentType("PatientConsent").setContent(bin); @@ -2801,7 +2830,7 @@ public class XmlParserDstu3Test { /* * If this fails, it's possibe the DocumentManifest structure is wrong: It should be - * + * * @Child(name = "p", type = {Attachment.class, ValueSet.class}, order=1, min=1, max=1, modifier=false, summary=true) */ assertNotNull(((Reference) actual.getContent().get(0).getP()).getResource()); @@ -2814,15 +2843,15 @@ public class XmlParserDstu3Test { public void testParseExtensionWithIdType() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; Patient pt = ourCtx.newXmlParser().parseResource(Patient.class, input); @@ -2835,22 +2864,22 @@ public class XmlParserDstu3Test { /** * See #426 - * + *

* Value type of FOO isn't a valid datatype */ @Test public void testParseExtensionWithInvalidType() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; Patient pt = ourCtx.newXmlParser().parseResource(Patient.class, input); @@ -2876,8 +2905,8 @@ public class XmlParserDstu3Test { public void testParseInvalidBoolean() { String resource = "\n" + - " \n" + - ""; + " \n" + + ""; IParser p = ourCtx.newXmlParser(); @@ -2913,12 +2942,12 @@ public class XmlParserDstu3Test { // This was changed from 0.5 to 1.0.0 String out = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; IParserErrorHandler errorHandler = mock(IParserErrorHandler.class); @@ -2934,37 +2963,61 @@ public class XmlParserDstu3Test { assertEquals("value", capt.getValue()); } + @Test + public void testParseMetaUpdatedDate() { + + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + Bundle b = ourCtx.newXmlParser().parseResource(Bundle.class, input); + + InstantType updated = b.getMeta().getLastUpdatedElement(); + assertEquals("2015-06-22T15:48:57.554-04:00", updated.getValueAsString()); + + } + @Test public void testParseMetadata() throws Exception { String content = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; Bundle b = ourCtx.newXmlParser().parseResource(Bundle.class, content); assertEquals(1, b.getEntry().size()); @@ -2987,43 +3040,6 @@ public class XmlParserDstu3Test { } - public static void compareXml(String content, String reEncoded) { - Diff d = DiffBuilder.compare(Input.fromString(content)) - .withTest(Input.fromString(reEncoded)) - .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)) - .checkForSimilar() - .ignoreWhitespace() // this is working with newest Saxon 9.8.0-2 (not worked with 9.7.0-15 - .ignoreComments() // this is not working even with newest Saxon 9.8.0-2 - .withComparisonController(ComparisonControllers.Default) - .build(); - - assertTrue(d.toString(), !d.hasDifferences()); - } - - @Test - public void testParseMetaUpdatedDate() { - - String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - - Bundle b = ourCtx.newXmlParser().parseResource(Bundle.class, input); - - InstantType updated = b.getMeta().getLastUpdatedElement(); - assertEquals("2015-06-22T15:48:57.554-04:00", updated.getValueAsString()); - - } - // TODO: this should work @Test @Ignore @@ -3032,11 +3048,11 @@ public class XmlParserDstu3Test { String htmlNoNs = "

AAABBBCCC
"; String htmlNs = htmlNoNs.replace("
", "
"); String res = "\n" + - " \n" + - " \n" + - " " + htmlNs + "\n" + - " \n" + - ""; + " \n" + + " \n" + + " " + htmlNs + "\n" + + " \n" + + ""; Patient p = ourCtx.newXmlParser().parseResource(Patient.class, res); assertEquals(htmlNs, p.getText().getDiv().getValueAsString()); @@ -3046,15 +3062,15 @@ public class XmlParserDstu3Test { public void testParseNestedExtensionsInvalid() { String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; try { ourCtx.newXmlParser().parseResource(Patient.class, input); @@ -3094,40 +3110,6 @@ public class XmlParserDstu3Test { assertEquals("Patient", reincarnatedPatient.getIdElement().getResourceType()); } - /** - * See #344 - */ - @Test - public void testParserIsCaseSensitive() { - Observation obs = new Observation(); - SampledData data = new SampledData(); - data.setData("1 2 3"); - data.setOrigin((SimpleQuantity) new SimpleQuantity().setValue(0L)); - data.setPeriod(1000L); - obs.setValue(data); - - IParser p = ourCtx.newXmlParser().setPrettyPrint(true).setParserErrorHandler(new StrictErrorHandler()); - String encoded = p.encodeResourceToString(obs); - ourLog.info(encoded); - - p.parseResource(encoded); - - try { - p.parseResource(encoded.replace("Observation", "observation")); - fail(); - } catch (DataFormatException e) { - assertEquals("DataFormatException at [[row,col {unknown-source}]: [1,1]]: Unknown resource type 'observation': Resource names are case sensitive, found similar name: 'Observation'", - e.getMessage()); - } - - try { - p.parseResource(encoded.replace("valueSampledData", "valueSampleddata")); - fail(); - } catch (DataFormatException e) { - assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Unknown element 'valueSampleddata' found during parse", e.getMessage()); - } - } - @Test(expected = DataFormatException.class) public void testParseWithInvalidLocalRef() throws IOException { String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8); @@ -3173,10 +3155,10 @@ public class XmlParserDstu3Test { public void testParseXmlExtensionWithoutUrl() { String input = "\n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + ""; IParser parser = ourCtx.newXmlParser(); parser.setParserErrorHandler(new LenientErrorHandler()); @@ -3203,10 +3185,10 @@ public class XmlParserDstu3Test { public void testParseXmlModifierExtensionWithoutUrl() { String input = "\n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + ""; IParser parser = ourCtx.newXmlParser(); parser.setParserErrorHandler(new LenientErrorHandler()); @@ -3226,6 +3208,40 @@ public class XmlParserDstu3Test { } + /** + * See #344 + */ + @Test + public void testParserIsCaseSensitive() { + Observation obs = new Observation(); + SampledData data = new SampledData(); + data.setData("1 2 3"); + data.setOrigin((SimpleQuantity) new SimpleQuantity().setValue(0L)); + data.setPeriod(1000L); + obs.setValue(data); + + IParser p = ourCtx.newXmlParser().setPrettyPrint(true).setParserErrorHandler(new StrictErrorHandler()); + String encoded = p.encodeResourceToString(obs); + ourLog.info(encoded); + + p.parseResource(encoded); + + try { + p.parseResource(encoded.replace("Observation", "observation")); + fail(); + } catch (DataFormatException e) { + assertEquals("DataFormatException at [[row,col {unknown-source}]: [1,1]]: Unknown resource type 'observation': Resource names are case sensitive, found similar name: 'Observation'", + e.getMessage()); + } + + try { + p.parseResource(encoded.replace("valueSampledData", "valueSampleddata")); + fail(); + } catch (DataFormatException e) { + assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Unknown element 'valueSampleddata' found during parse", e.getMessage()); + } + } + /** * See #551 */ @@ -3245,24 +3261,24 @@ public class XmlParserDstu3Test { /** * See #339 - * + *

* https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing */ @Test public void testXxe() { String input = "" + - "" + - "]>" + - "" + - "" + - "

TEXT &xxe; TEXT
" + - "" + - "
" + - "" + - "
" + - ""; + "" + + "]>" + + "" + + "" + + "
TEXT &xxe; TEXT
" + + "
" + + "
" + + "" + + "
" + + "
"; ourLog.info(input); @@ -3275,35 +3291,22 @@ public class XmlParserDstu3Test { } - @Test - public void testBaseUrlFooResourceCorrectlySerializedInExtensionValueReference() { - String refVal = "http://my.org/FooBar"; - - Patient fhirPat = new Patient(); - fhirPat.addExtension().setUrl("x1").setValue(new Reference(refVal)); - - IParser parser = ourCtx.newXmlParser(); - - String output = parser.encodeResourceToString(fhirPat); - System.out.println("output: " + output); - - // Deserialize then check that valueReference value is still correct - fhirPat = parser.parseResource(Patient.class, output); - - List extlst = fhirPat.getExtensionsByUrl("x1"); - Assert.assertEquals(1, extlst.size()); - Assert.assertEquals(refVal, ((Reference) extlst.get(0).getValue()).getReference()); - } - @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } - public static void main(String[] args) { - IGenericClient c = ourCtx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open"); - // c.registerInterceptor(new LoggingInterceptor(true)); - c.read().resource("Patient").withId("324").execute(); + public static void compareXml(String content, String reEncoded) { + Diff d = DiffBuilder.compare(Input.fromString(content)) + .withTest(Input.fromString(reEncoded)) + .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)) + .checkForSimilar() + .ignoreWhitespace() // this is working with newest Saxon 9.8.0-2 (not worked with 9.7.0-15 + .ignoreComments() // this is not working even with newest Saxon 9.8.0-2 + .withComparisonController(ComparisonControllers.Default) + .build(); + + assertTrue(d.toString(), !d.hasDifferences()); } @ResourceDef(name = "Patient") diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java index a0cf8a51031..86f43d9114c 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java @@ -1,32 +1,5 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.IOUtils; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; -import org.junit.*; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; @@ -38,15 +11,46 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.util.*; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.HumanName; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Patient; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.*; public class SearchR4Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class); private static CloseableHttpClient ourClient; private static FhirContext ourCtx = FhirContext.forR4(); private static TokenAndListParam ourIdentifiers; private static String ourLastMethod; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchR4Test.class); private static int ourPort; private static Server ourServer; @@ -57,74 +61,63 @@ public class SearchR4Test { ourIdentifiers = null; } - @Test - public void testSearchNormal() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar"); + private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException { CloseableHttpResponse status = ourClient.execute(httpGet); + Bundle bundle; try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseContent); assertEquals(200, status.getStatusLine().getStatusCode()); - - assertEquals("search", ourLastMethod); - - assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem()); - assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); + EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim()); + assertEquals(theExpectEncoding, ct); + bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent); + assertEquals(10, bundle.getEntry().size()); + String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertNotNull(linkNext); } finally { IOUtils.closeQuietly(status.getEntity().getContent()); } - + return bundle; } @Test - public void testSearchWithInvalidChain() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar"); - CloseableHttpResponse status = ourClient.execute(httpGet); - try { - String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(responseContent); - assertEquals(400, status.getStatusLine().getStatusCode()); - - OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent); - assertEquals( - "Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)", - oo.getIssueFirstRep().getDiagnostics()); - } finally { - IOUtils.closeQuietly(status.getEntity().getContent()); - } - - } - - - @Test - public void testPagingPreservesEncodingJson() throws Exception { + public void testPagingPreservesElements() throws Exception { HttpGet httpGet; String linkNext; Bundle bundle; + String linkSelf; // Initial search - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json"); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name"); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + assertThat(toJson(bundle), not(containsString("active"))); + linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl(); + assertThat(linkSelf, containsString("_elements=name")); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=json")); + assertThat(linkNext, containsString("_elements=name")); + + ourLog.info(toJson(bundle)); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + assertThat(toJson(bundle), not(containsString("active"))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=json")); + assertThat(linkNext, containsString("_elements=name")); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + assertThat(toJson(bundle), not(containsString("active"))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=json")); + assertThat(linkNext, containsString("_elements=name")); // Fetch the next page httpGet = new HttpGet(linkNext); bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + assertThat(toJson(bundle), not(containsString("active"))); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=json")); + assertThat(linkNext, containsString("_elements=name")); } @@ -161,34 +154,35 @@ public class SearchR4Test { } @Test - public void testPagingPreservesEncodingXml() throws Exception { + public void testPagingPreservesEncodingJson() throws Exception { HttpGet httpGet; String linkNext; Bundle bundle; // Initial search - httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml"); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json"); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); + assertThat(toJson(bundle), containsString("active")); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=xml")); + assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=xml")); + assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=xml")); + assertThat(linkNext, containsString("_format=json")); // Fetch the next page httpGet = new HttpGet(linkNext); - bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON); linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertThat(linkNext, containsString("_format=xml")); + assertThat(linkNext, containsString("_format=json")); } @@ -260,26 +254,76 @@ public class SearchR4Test { } - private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException { - CloseableHttpResponse status = ourClient.execute(httpGet); + @Test + public void testPagingPreservesEncodingXml() throws Exception { + HttpGet httpGet; + String linkNext; Bundle bundle; + + // Initial search + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml"); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertThat(linkNext, containsString("_format=xml")); + + // Fetch the next page + httpGet = new HttpGet(linkNext); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertThat(linkNext, containsString("_format=xml")); + + // Fetch the next page + httpGet = new HttpGet(linkNext); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertThat(linkNext, containsString("_format=xml")); + + // Fetch the next page + httpGet = new HttpGet(linkNext); + bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML); + linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); + assertThat(linkNext, containsString("_format=xml")); + + } + + @Test + public void testSearchNormal() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar"); + CloseableHttpResponse status = ourClient.execute(httpGet); try { String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseContent); assertEquals(200, status.getStatusLine().getStatusCode()); - EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim()); - assertEquals(theExpectEncoding, ct); - bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent); - assertEquals(10, bundle.getEntry().size()); - String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl(); - assertNotNull(linkNext); + + assertEquals("search", ourLastMethod); + + assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem()); + assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue()); } finally { IOUtils.closeQuietly(status.getEntity().getContent()); } - return bundle; + + } + + @Test + public void testSearchWithInvalidChain() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar"); + CloseableHttpResponse status = ourClient.execute(httpGet); + try { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(responseContent); + assertEquals(400, status.getStatusLine().getStatusCode()); + + OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent); + assertEquals( + "Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)", + oo.getIssueFirstRep().getDiagnostics()); + } finally { + IOUtils.closeQuietly(status.getEntity().getContent()); + } + } - @Test public void testSearchWithPostAndInvalidParameters() throws Exception { IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort); @@ -293,14 +337,14 @@ public class SearchR4Test { client.registerInterceptor(interceptor); try { client - .search() - .forResource(Patient.class) - .where(new StringClientParam("foo").matches().value("bar")) - .prettyPrint() - .usingStyle(SearchStyleEnum.POST) - .returnBundle(org.hl7.fhir.r4.model.Bundle.class) - .encodedJson() - .execute(); + .search() + .forResource(Patient.class) + .where(new StringClientParam("foo").matches().value("bar")) + .prettyPrint() + .usingStyle(SearchStyleEnum.POST) + .returnBundle(org.hl7.fhir.r4.model.Bundle.class) + .encodedJson() + .execute(); fail(); } catch (InvalidRequestException e) { assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]")); @@ -308,6 +352,10 @@ public class SearchR4Test { } + private String toJson(Bundle theBundle) { + return ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle); + } + @AfterClass public static void afterClassClearContext() throws Exception { ourServer.stop(); @@ -349,16 +397,17 @@ public class SearchR4Test { @SuppressWarnings("rawtypes") @Search() public List search( - @RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { + @RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) { ourLastMethod = "search"; ourIdentifiers = theIdentifiers; ArrayList retVal = new ArrayList(); - + for (int i = 0; i < 200; i++) { Patient patient = new Patient(); patient.addName(new HumanName().setFamily("FAMILY")); + patient.setActive(true); patient.getIdElement().setValue("Patient/" + i); - retVal.add((Patient) patient); + retVal.add(patient); } return retVal; } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0c3ed55d06b..65d74758394 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -175,11 +175,26 @@ was not encoded correctly. Thanks to Malcolm McRoberts for the pull request with fix and test case! + + Bundle resources did not have their version encoded when serializing + in FHIR resource (XML/JSON) format. + The Binary resource endpoint now supports the `X-Security-Context` header when reading or writing Binary contents using their native Content-Type (i.e exchanging the raw binary with the server, as opposed to exchanging a FHIR resource). + + When paging through multiple pages of search results, if the + client had requested a subset of resources to be returned using the + _elements
]]> parameter, the elements list + was lost after the first page of results. + In addition, elements will not remove elements from + search/history Bundles (i.e. elements from the Bundle itself, as opposed + to elements in the entry resources) unless the Bundle elements are + explicitly listed, e.g. _include=Bundle.total
]]>. + Thanks to @parisni for reporting! + From 395c8b6acc4141721f6bae01b482b631a528b092 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 7 Nov 2017 09:58:42 -0500 Subject: [PATCH 07/24] Add test for #773 --- .../jpa/dao/r4/FhirResourceDaoR4Test.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java index 567aa1aa49a..04a686da42c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java @@ -55,6 +55,7 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { public final void after() { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); + myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete()); } private void assertGone(IIdType theId) { @@ -98,6 +99,29 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test { } + /** + * See #773 + */ + @Test + public void testDeleteResourceWithOutboundDeletedResources() { + myDaoConfig.setEnforceReferentialIntegrityOnDelete(false); + + Organization org = new Organization(); + org.setId("ORG"); + org.setName("ORG"); + myOrganizationDao.update(org); + + Patient pat = new Patient(); + pat.setId("PAT"); + pat.setActive(true); + pat.setManagingOrganization(new Reference("Organization/ORG")); + myPatientDao.update(pat); + + myOrganizationDao.delete(new IdType("Organization/ORG")); + + myPatientDao.delete(new IdType("Patient/PAT")); + } + @Before public void beforeDisableResultReuse() { From f91977cbace935df95d43eb9b0fded260f213e98 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 8 Nov 2017 16:53:39 -0500 Subject: [PATCH 08/24] Beef up logging in tests --- .../dao/dstu2/FhirResourceDaoDstu2Test.java | 24 +++++++++++++++++++ .../ResourceProviderInterceptorDstu3Test.java | 16 +++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java index c03923110a2..1c3a5d470fc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2Test.java @@ -56,6 +56,30 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test { myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical()); } + /** + * See #773 + */ + @Test + public void testDeleteResourceWithOutboundDeletedResources() { + myDaoConfig.setEnforceReferentialIntegrityOnDelete(false); + + Organization org = new Organization(); + org.setId("ORG"); + org.setName("ORG"); + myOrganizationDao.update(org); + + Patient pat = new Patient(); + pat.setId("PAT"); + pat.setActive(true); + pat.setManagingOrganization(new ResourceReferenceDt("Organization/ORG")); + myPatientDao.update(pat); + + myOrganizationDao.delete(new IdDt("Organization/ORG")); + + myPatientDao.delete(new IdDt("Patient/PAT")); + } + + private void assertGone(IIdType theId) { try { assertNotGone(theId); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderInterceptorDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderInterceptorDstu3Test.java index 29798cf84bf..2a4e87c050d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderInterceptorDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderInterceptorDstu3Test.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jpa.provider.dstu3; import ca.uhn.fhir.jpa.provider.r4.ResourceProviderInterceptorR4Test; -import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -61,8 +60,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs public void before() throws Exception { super.before(); - myServerInterceptor = mock(IServerInterceptor.class); - myDaoInterceptor = mock(IServerInterceptor.class); + myServerInterceptor = mock(IServerInterceptor.class, withSettings().verboseLogging()); + myDaoInterceptor = mock(IServerInterceptor.class, withSettings().verboseLogging()); resetServerInterceptor(); @@ -125,6 +124,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs public void testCreateResourceInTransaction() throws IOException, ServletException { String methodName = "testCreateResourceInTransaction"; + ourLog.info("** Starting {}", methodName); + Patient pt = new Patient(); pt.addName().setFamily(methodName); @@ -140,6 +141,11 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs resetServerInterceptor(); + ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + verify(myDaoInterceptor, times(0)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + verify(myServerInterceptor, times(0)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); + HttpPost post = new HttpPost(ourServerBase + "/"); post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); CloseableHttpResponse response = ourHttpClient.execute(post); @@ -153,8 +159,8 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs * Server Interceptor */ - ArgumentCaptor ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); - ArgumentCaptor opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); + ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class); + opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class); verify(myServerInterceptor, times(2)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture()); assertEquals(RestOperationTypeEnum.TRANSACTION, opTypeCaptor.getAllValues().get(0)); assertEquals(null, ardCaptor.getAllValues().get(0).getResourceType()); From 8dbcd29c8d6ed79969cf753490c76b83aedd9e1b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 8 Nov 2017 17:12:06 -0500 Subject: [PATCH 09/24] Dont reuse ports in tests --- .../src/main/java/ca/uhn/fhir/util/PortUtil.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java index 620e36dbbc4..0ec0fe86848 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java @@ -21,12 +21,14 @@ package ca.uhn.fhir.util; */ import java.net.ServerSocket; +import java.util.LinkedHashSet; /** * Provides server ports */ @CoverageIgnore public class PortUtil { + private static LinkedHashSet ourPorts = new LinkedHashSet<>(); /* * Non instantiable @@ -41,9 +43,13 @@ public class PortUtil { public static int findFreePort() { ServerSocket server; try { - server = new ServerSocket(0); - int port = server.getLocalPort(); - server.close(); + int port; + do { + server = new ServerSocket(0); + port = server.getLocalPort(); + server.close(); + } while (!ourPorts.add(port)); + Thread.sleep(500); return port; } catch (Exception e) { From 32a4a081d9666974fcce8c6c06eb8286764a609d Mon Sep 17 00:00:00 2001 From: James Date: Wed, 8 Nov 2017 20:01:48 -0500 Subject: [PATCH 10/24] More test work -- Ahhhhh travis grr --- .../uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 76dbadc319a..5a458068ef2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -3155,11 +3155,11 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.empty()); - assertThat(sw.getMillis(), lessThan(1000L)); - // If this fails under load, try increasing the throttle above assertEquals(null, found.getTotalElement().getValue()); assertEquals(1, found.getEntry().size()); + assertThat(sw.getMillis(), lessThan(1000L)); + } @Test @@ -3214,11 +3214,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.empty()); - assertThat(sw.getMillis(), lessThan(1000L)); - // If this fails under load, try increasing the throttle above assertEquals(null, found.getTotalElement().getValue()); assertEquals(1, found.getEntry().size()); + assertThat(sw.getMillis(), lessThan(1000L)); } @Test From bb31e11d9a8f1d5acb8267fbd7dbffe6828687ab Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 13:22:17 -0500 Subject: [PATCH 11/24] Credit for #743 --- pom.xml | 4 ++++ src/changes/changes.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 6c50a8277a6..d1791c01835 100644 --- a/pom.xml +++ b/pom.xml @@ -370,6 +370,10 @@ malcolmm83 Malcolm McRoberts + + mouellet + Mathieu Ouellet + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 65d74758394..12871c6eba9 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -195,6 +195,10 @@ explicitly listed, e.g. _include=Bundle.total
]]>. Thanks to @parisni for reporting! + + Add an example which uses Spring Boot to stand up a complete JPA server implementation. + Thanks to Mathieu Ouellet for the contribution! + From f3f870c1687250f6f834666472737c6501f41f0c Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 13:31:49 -0500 Subject: [PATCH 12/24] Update description for spring boot --- src/changes/changes.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 12871c6eba9..dfd721fc87f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -196,7 +196,8 @@ Thanks to @parisni for reporting! - Add an example which uses Spring Boot to stand up a complete JPA server implementation. + Add support for Spring Boot for initializing a number of parts of the library, + as well as several examples. Thanks to Mathieu Ouellet for the contribution! From 7de898cdf3b3619be17739b5c0c9569cd9756e44 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 13:35:10 -0500 Subject: [PATCH 13/24] Credit for #747 --- pom.xml | 4 ++++ src/changes/changes.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 2eaa180d6d0..6b71e6fe75e 100644 --- a/pom.xml +++ b/pom.xml @@ -374,6 +374,10 @@ mouellet Mathieu Ouellet + + JiajingLiang + Jiajing Liang + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index dfd721fc87f..4863b724416 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -200,6 +200,11 @@ as well as several examples. Thanks to Mathieu Ouellet for the contribution! + + JPA server now has lucene index support moved to separate classes from the entity + classes in order to facilitate support for ElasticSearch. Thanks to Jiang Liang + for the pull request! + From 46f1b0b5320e2f52107115d7a3f5ae423dad8b2d Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 13:50:39 -0500 Subject: [PATCH 14/24] Config changes to compiler --- hapi-deployable-pom/pom.xml | 6 +++--- pom.xml | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 8b0168ae690..93b7bdaed96 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -116,10 +116,10 @@ spring-jcl - - changelog.txt + + changelog.txt javac.bat - + diff --git a/pom.xml b/pom.xml index 6b71e6fe75e..1fda9249c4a 100644 --- a/pom.xml +++ b/pom.xml @@ -1048,7 +1048,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.2 + 3.7.0 1.7 1.7 @@ -1061,6 +1061,10 @@ 1.8 true UTF-8 + + true + 128m + 512m From 6ca1498277fbb55a07b27e1da28f8d95e60b2581 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 15:49:12 -0500 Subject: [PATCH 15/24] Fix tests for spring boot --- appveyor.yml | 5 +++ .../search/LuceneSearchMappingFactory.java | 38 +++++++++---------- .../ca/uhn/fhir/jpa/config/TestR4Config.java | 2 +- .../FhirAutoConfigurationTest.java | 3 +- .../src/main/resources/application.yml | 2 + 5 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000000..a56e90eafa3 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,5 @@ +version: 1.0.{build} +image: Visual Studio 2017 +cache: C:\Users\appveyor\.m2\repository +build_script: +- cmd: mvn install \ No newline at end of file diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java index fdf74d57ba3..1f950f28ed4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/LuceneSearchMappingFactory.java @@ -23,28 +23,28 @@ public class LuceneSearchMappingFactory { SearchMapping mapping = new SearchMapping(); mapping.analyzerDef("autocompleteEdgeAnalyzer", PatternTokenizerFactory.class) - .tokenizerParam("pattern", "(.*)") - .tokenizerParam("group", "1") - .filter(LowerCaseFilterFactory.class) - .filter(StopFilterFactory.class) - .filter(EdgeNGramFilterFactory.class) - .param("minGramSize", "3") - .param("maxGramSize", "50") + .tokenizerParam("pattern", "(.*)") + .tokenizerParam("group", "1") + .filter(LowerCaseFilterFactory.class) + .filter(StopFilterFactory.class) + .filter(EdgeNGramFilterFactory.class) + .param("minGramSize", "3") + .param("maxGramSize", "50") .analyzerDef("autocompletePhoneticAnalyzer", StandardTokenizerFactory.class) - .filter(StandardFilterFactory.class) - .filter(StopFilterFactory.class) - .filter(PhoneticFilterFactory.class) - .param("encoder", "DoubleMetaphone") - .filter(SnowballPorterFilterFactory.class) - .param("language", "English") + .filter(StandardFilterFactory.class) + .filter(StopFilterFactory.class) + .filter(PhoneticFilterFactory.class) + .param("encoder", "DoubleMetaphone") + .filter(SnowballPorterFilterFactory.class) + .param("language", "English") .analyzerDef("autocompleteNGramAnalyzer", StandardTokenizerFactory.class) - .filter(WordDelimiterFilterFactory.class) - .filter(LowerCaseFilterFactory.class) - .filter(NGramFilterFactory.class) - .param("minGramSize", "3") - .param("maxGramSize", "20") + .filter(WordDelimiterFilterFactory.class) + .filter(LowerCaseFilterFactory.class) + .filter(NGramFilterFactory.class) + .param("minGramSize", "3") + .param("maxGramSize", "20") .analyzerDef("standardAnalyzer", StandardTokenizerFactory.class) - .filter(LowerCaseFilterFactory.class) + .filter(LowerCaseFilterFactory.class) .analyzerDef("exactAnalyzer", StandardTokenizerFactory.class) .analyzerDef("conceptParentPidsAnalyzer", WhitespaceTokenizerFactory.class); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index f0ee6652136..d40ea5d2284 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -126,7 +126,7 @@ public class TestR4Config extends BaseJavaConfigR4 { extraProperties.put("hibernate.show_sql", "false"); extraProperties.put("hibernate.hbm2ddl.auto", "update"); extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect"); - extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); + extraProperties.put("hibernate.search.model_mapping", ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "ram"); extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); extraProperties.put("hibernate.search.autoregister_listeners", "true"); diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java index 7f9f8fc16ff..3b3023e4eff 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/test/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfigurationTest.java @@ -76,7 +76,8 @@ public class FhirAutoConfigurationTest { PropertyPlaceholderAutoConfiguration.class, FhirAutoConfiguration.class), "hapi.fhir.version:DSTU3", - "spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles"); + "spring.jpa.properties.hibernate.search.default.indexBase:target/lucenefiles", + "spring.jpa.properties.hibernate.search.model_mapping:ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory"); assertThat(this.context.getBeansOfType(DaoConfig.class)).hasSize(1); assertThat(this.context.getBeansOfType(Dstu3.class)).hasSize(1); } diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml index a28ca61340f..2c2f591eb69 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jpa/src/main/resources/application.yml @@ -11,6 +11,8 @@ spring: hibernate.search.default.directory_provider: filesystem hibernate.search.default.indexBase: target/lucenefiles hibernate.search.lucene_version: LUCENE_CURRENT + hibernate.search.model_mapping: ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory + h2: console: enabled: true From 99320c3b9620daef4bc7e5f32c45e00f027d2f9b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 16:17:20 -0500 Subject: [PATCH 16/24] More memory for compiler --- hapi-fhir-structures-dstu3/pom.xml | 7 ------- pom.xml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index f303ccd1785..d796ef60380 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -70,13 +70,6 @@ true - - org.apache.maven.plugins - maven-compiler-plugin - - true - - diff --git a/pom.xml b/pom.xml index 1fda9249c4a..dc99e58019f 100644 --- a/pom.xml +++ b/pom.xml @@ -1064,7 +1064,7 @@ true 128m - 512m + 1024m From d5425fba5e5ed9a634b2b81ecdc46c35165c96fc Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 13 Nov 2017 20:59:33 -0500 Subject: [PATCH 17/24] Try to get appveyor working more --- appveyor.yml | 12 +++++++----- pom.xml | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a56e90eafa3..d40c830d242 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,7 @@ -version: 1.0.{build} -image: Visual Studio 2017 -cache: C:\Users\appveyor\.m2\repository -build_script: -- cmd: mvn install \ No newline at end of file +version: {build} +image: Visual Studio 2017 +cache: + - C:\maven\ + - C:\Users\appveyor\.m2 +build_script: + - cmd: mvn -P MINPARALLEL install diff --git a/pom.xml b/pom.xml index dc99e58019f..aa9b1c4c5e3 100644 --- a/pom.xml +++ b/pom.xml @@ -1064,7 +1064,7 @@ true 128m - 1024m + 1600m From aaed0c58767438bafd5bc46a77ca2a17722228e0 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Mon, 13 Nov 2017 21:02:39 -0500 Subject: [PATCH 18/24] Add some logging --- .../java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java | 3 +++ .../src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java | 1 + .../src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java | 1 + 3 files changed, 5 insertions(+) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 49950d34dd3..764646846c5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -662,6 +662,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (myDaoConfig.getCountSearchResultsUpTo() == null || myDaoConfig.getCountSearchResultsUpTo() <= 0 || myDaoConfig.getCountSearchResultsUpTo() <= numSynced) { + + ourLog.trace("Have synced {} and want count <= {}", numSynced, myDaoConfig.getCountSearchResultsUpTo()); + myInitialCollectionLatch.countDown(); } } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java index ec38c09ad66..095ca6c39fd 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java @@ -66,6 +66,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { retVal.setAllowExternalReferences(true); retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu2"); retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu2"); + retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO); return retVal; } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java index cf15684ddd6..1f5dcb028d0 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java @@ -55,6 +55,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { retVal.setAllowExternalReferences(true); retVal.getTreatBaseUrlsAsLocal().add("http://fhirtest.uhn.ca/baseDstu3"); retVal.getTreatBaseUrlsAsLocal().add("https://fhirtest.uhn.ca/baseDstu3"); + retVal.setCountSearchResultsUpTo(TestR4Config.COUNT_SEARCH_RESULTS_UP_TO); return retVal; } From 0d0300623f15426276cbfff1e70ece4d32d49c5b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 15 Nov 2017 18:31:03 +0100 Subject: [PATCH 19/24] Streamline search coordinator to improve performance --- appveyor.yml | 2 +- .../jpa/search/SearchCoordinatorSvcImpl.java | 135 +++++++++++++----- 2 files changed, 99 insertions(+), 38 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d40c830d242..c8a3ee8500a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: {build} +version: 1.0.{build} image: Visual Studio 2017 cache: - C:\maven\ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 49950d34dd3..6f2a727e59e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -19,39 +19,51 @@ package ca.uhn.fhir.jpa.search; * limitations under the License. * #L%family */ -import java.util.*; -import java.util.concurrent.*; - -import javax.persistence.EntityManager; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IDao; +import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.SearchParameterMap; +import ca.uhn.fhir.jpa.dao.data.ISearchDao; +import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; +import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; +import ca.uhn.fhir.jpa.entity.*; +import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.method.PageMethodBinding; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; -import org.springframework.transaction.*; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.*; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.entity.*; -import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.model.api.Include; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.server.SimpleBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.rest.server.method.PageMethodBinding; +import javax.persistence.EntityManager; +import java.util.*; +import java.util.concurrent.*; public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { public static final int DEFAULT_SYNC_SIZE = 250; @@ -420,6 +432,19 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } } + /** + * A search task is a Callable task that runs in + * a thread pool to handle an individual search. One instance + * is created for any requested search and runs from the + * beginning to the end of the search. + * + * Understand: + * This class executes in its own thread separate from the + * web server client thread that made the request. We do that + * so that we can return to the client as soon as possible, + * but keep the search going in the background (and have + * the next page of results ready to go when the client asks). + */ public class SearchTask implements Callable { private final IDao myCallingDao; @@ -434,6 +459,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { private int myCountSaved = 0; private String mySearchUuid; + /** + * Constructor + */ public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) { mySearch = theSearch; myCallingDao = theCallingDao; @@ -443,6 +471,11 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { mySearchUuid = theSearchUuid; } + /** + * This method is called by the server HTTP thread, and + * will block until at least one page of results have been + * fetched from the DB, and will never block after that. + */ public Integer awaitInitialSync() { ourLog.trace("Awaiting initial sync"); do { @@ -451,6 +484,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { break; } } catch (InterruptedException e) { + // Shouldn't happen throw new InternalErrorException(e); } } while (mySearch.getStatus() == SearchStatusEnum.LOADING); @@ -459,11 +493,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { return mySearch.getTotalCount(); } + /** + * This is the method which actually performs the search. + * It is called automatically by the thread pool. + */ @Override public Void call() throws Exception { StopWatch sw = new StopWatch(); try { + // Create an initial search in the DB and give it an ID saveSearch(); TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); @@ -480,7 +519,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { } catch (Throwable t) { /* - * Don't print a stack trace for client errors.. that's just noisy + * Don't print a stack trace for client errors (i.e. requests that + * aren't valid because the client screwed up).. that's just noise + * in the logs and who needs that. */ boolean logged = false; if (t instanceof BaseServerResponseException) { @@ -535,13 +576,27 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { Class resourceTypeClass = myContext.getResourceDefinition(myResourceType).getImplementingClass(); ISearchBuilder sb = myCallingDao.newSearchBuilder(); sb.setType(resourceTypeClass, myResourceType); - Iterator theResultIter = sb.createQuery(myParams, mySearchUuid); + Iterator theResultIterator = sb.createQuery(myParams, mySearchUuid); - while (theResultIter.hasNext()) { - myUnsyncedPids.add(theResultIter.next()); - if (myUnsyncedPids.size() >= mySyncSize) { - saveUnsynced(theResultIter); + while (theResultIterator.hasNext()) { + myUnsyncedPids.add(theResultIterator.next()); + + boolean shouldSync = myUnsyncedPids.size() >= mySyncSize; + + if (myDaoConfig.getCountSearchResultsUpTo() != null && + myDaoConfig.getCountSearchResultsUpTo() > 0 && + myDaoConfig.getCountSearchResultsUpTo() < myUnsyncedPids.size()) { + shouldSync = false; } + + if (myUnsyncedPids.size() > 50000) { + shouldSync = true; + } + + if (shouldSync) { + saveUnsynced(theResultIterator); + } + if (myLoadingThrottleForUnitTests != null) { try { Thread.sleep(myLoadingThrottleForUnitTests); @@ -549,9 +604,12 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { // ignore } } + + // Check if an abort got requested Validate.isTrue(myAbortRequested == false, "Abort has been requested"); + } - saveUnsynced(theResultIter); + saveUnsynced(theResultIterator); } public CountDownLatch getCompletionLatch() { @@ -648,22 +706,25 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { mySearch.setStatus(SearchStatusEnum.FINISHED); } } + mySearch.setNumFound(myCountSaved); + + int numSynced; + synchronized (mySyncedPids) { + numSynced = mySyncedPids.size(); + } + + if (myDaoConfig.getCountSearchResultsUpTo() == null || + myDaoConfig.getCountSearchResultsUpTo() <= 0 || + myDaoConfig.getCountSearchResultsUpTo() <= numSynced) { + myInitialCollectionLatch.countDown(); + } + doSaveSearch(); } }); - int numSynced; - synchronized (mySyncedPids) { - numSynced = mySyncedPids.size(); - } - - if (myDaoConfig.getCountSearchResultsUpTo() == null || - myDaoConfig.getCountSearchResultsUpTo() <= 0 || - myDaoConfig.getCountSearchResultsUpTo() <= numSynced) { - myInitialCollectionLatch.countDown(); - } } } From 607abd34a30fe70d3d897c280863b0eb396072d0 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 15 Nov 2017 12:42:53 -0500 Subject: [PATCH 20/24] Try to get appveyor working --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c8a3ee8500a..037f9f0e7a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,4 +4,4 @@ cache: - C:\maven\ - C:\Users\appveyor\.m2 build_script: - - cmd: mvn -P MINPARALLEL install + - cmd: mvn -P MINPARALLEL,ALLMODULES install From d922775639c2e119c28b1ab40ec24b5a658973ea Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 15 Nov 2017 12:56:47 -0500 Subject: [PATCH 21/24] Credit for #755 --- src/changes/changes.xml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4863b724416..21cee568c1f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -154,7 +154,7 @@ JAX-RS client framework now supports the ability to register your own JAX-RS Component Classes against the client, as well as better documentation about thread safety. Thanks - to Sébastien Rivière for the pull request! + to Sébastien Rivière for the pull request! Processing of the If-Modified-Since header on FHIR read operations was reversed, @@ -205,6 +205,13 @@ classes in order to facilitate support for ElasticSearch. Thanks to Jiang Liang for the pull request! + + A new client interceptor has been added called + AdditionalRequestHeadersInterceptor, which allows + a developer to add additional custom headers to a + client requests. + Thanks to Clayton Bodendein for the pull request! + @@ -570,7 +577,7 @@ Bundle bundle = client.search().forResource(Patient.class) Correct an issue in the validator (DSTU3/R4) where elements were not always correctly validated if the element contained only a profiled extension. Thanks - to Sébastien Rivière for the pull request! + to Sébastien Rivière for the pull request! Testing UI now has a dropdown for modifiers on token search. Thanks @@ -584,7 +591,7 @@ Bundle bundle = client.search().forResource(Patient.class) Extensions on ID datatypes were not parsed or serialized correctly. Thanks to - Stephen Rivière for the pull request! + Stephen Rivière for the pull request! Fix a bug in REST Hook Subscription interceptors which prevented subscriptions @@ -905,7 +912,7 @@ Bundle bundle = client.search().forResource(Patient.class) DaoConfig#setAllowInlineMatchUrlReferences() now defaults to true
]]> since inline conditional references - are now a part of the FHIR specification. Thanks to Jan Dědek for + are now a part of the FHIR specification. Thanks to Jan Dědek for pointing this out! @@ -1885,7 +1892,7 @@ Bundle bundle = client.search().forResource(Patient.class) Parser failed to parse resources containing an extension with a value type of - "id". Thanks to Raphael Mäder for reporting! + "id". Thanks to Raphael Mäder for reporting! When committing a transaction in JPA server @@ -2147,7 +2154,7 @@ Bundle bundle = client.search().forResource(Patient.class) array instead of a string. Thanks to David Hay for reporting! - Sébastien Rivière contributed an excellent pull request which adds a + Sébastien Rivière contributed an excellent pull request which adds a number of enhancements to JAX-RS module: From a87323fa08736009144c09be5c06aacf917291b7 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 17 Nov 2017 09:53:44 +0100 Subject: [PATCH 22/24] Add tests --- examples/pom.xml | 6 + .../SimpleRequestHeaderInterceptor.java | 2 + .../provider/r4/ResourceProviderR4Test.java | 10 +- ...tionalRequestHeadersInterceptorR4Test.java | 2202 +++++++++++++++++ 4 files changed, 2216 insertions(+), 4 deletions(-) create mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AdditionalRequestHeadersInterceptorR4Test.java diff --git a/examples/pom.xml b/examples/pom.xml index 11f732fb672..9e6fd9ab937 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -92,6 +92,12 @@ org.springframework spring-web + + + org.slf4j + slf4j-simple + + diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/SimpleRequestHeaderInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/SimpleRequestHeaderInterceptor.java index 72edbf83fa7..ca850dba4b3 100644 --- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/SimpleRequestHeaderInterceptor.java +++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/SimpleRequestHeaderInterceptor.java @@ -32,6 +32,8 @@ import org.apache.commons.lang3.Validate; /** * This interceptor adds an arbitrary header to requests made by this client. Both the * header name and the header value are specified by the calling code. + * + * @see AdditionalRequestHeadersInterceptor for a more advanced version of this interceptor which can add multiple headers */ public class SimpleRequestHeaderInterceptor implements IClientInterceptor { diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 5a458068ef2..5c6981cccb5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -3149,16 +3149,18 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(1) .execute(); - ourLog.info("** Done searching with count of 1"); + ourLog.info("** Done searching in {}ms with count of 1", sw.getMillis()); ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.empty()); + String msg = "Total is " + found.getTotalElement().getValue() + " and took " + sw.getMillis() + " millis"; + // If this fails under load, try increasing the throttle above - assertEquals(null, found.getTotalElement().getValue()); - assertEquals(1, found.getEntry().size()); - assertThat(sw.getMillis(), lessThan(1000L)); + assertEquals(msg,null, found.getTotalElement().getValue()); + assertEquals(msg, 1, found.getEntry().size()); + assertThat(msg, sw.getMillis(), lessThan(1000L)); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AdditionalRequestHeadersInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AdditionalRequestHeadersInterceptorR4Test.java new file mode 100644 index 00000000000..5989618acb0 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/AdditionalRequestHeadersInterceptorR4Test.java @@ -0,0 +1,2202 @@ +package ca.uhn.fhir.rest.server.interceptor; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.api.AddProfileTagEnum; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.server.IRequestOperationCallback; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; +import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; +import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum; +import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.*; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.*; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.*; + +public class AdditionalRequestHeadersInterceptorR4Test { + + private static final String ERR403 = "{\"resourceType\":\"OperationOutcome\",\"issue\":[{\"severity\":\"error\",\"code\":\"processing\",\"diagnostics\":\"Access denied by default policy (no applicable rules)\"}]}"; + private static CloseableHttpClient ourClient; + private static String ourConditionalCreateId; + private static FhirContext ourCtx = FhirContext.forR4(); + private static boolean ourHitMethod; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AdditionalRequestHeadersInterceptorR4Test.class); + private static int ourPort; + private static List ourReturn; + private static Server ourServer; + private static RestfulServer ourServlet; + + @Before + public void before() { + ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER); + for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) { + ourServlet.unregisterInterceptor(next); + } + ourReturn = null; + ourHitMethod = false; + ourConditionalCreateId = "1123"; + } + + private Resource createCarePlan(Integer theId, String theSubjectId) { + CarePlan retVal = new CarePlan(); + if (theId != null) { + retVal.setId(new IdType("CarePlan", (long) theId)); + } + retVal.setSubject(new Reference("Patient/" + theSubjectId)); + return retVal; + } + + private HttpEntity createFhirResourceEntity(IBaseResource theResource) { + String out = ourCtx.newJsonParser().encodeResourceToString(theResource); + return new StringEntity(out, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")); + } + + private Resource createObservation(Integer theId, String theSubjectId) { + Observation retVal = new Observation(); + if (theId != null) { + retVal.setId(new IdType("Observation", (long) theId)); + } + retVal.getCode().setText("OBS"); + retVal.setSubject(new Reference(theSubjectId)); + return retVal; + } + + private Resource createPatient(Integer theId) { + Patient retVal = new Patient(); + if (theId != null) { + retVal.setId(new IdType("Patient", (long) theId)); + } + retVal.addName().setFamily("FAM"); + return retVal; + } + + private Resource createPatient(Integer theId, int theVersion) { + Resource retVal = createPatient(theId); + retVal.setId(retVal.getIdElement().withVersion(Integer.toString(theVersion))); + return retVal; + } + + private String extractResponseAndClose(HttpResponse status) throws IOException { + if (status.getEntity() == null) { + return null; + } + String responseContent; + responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(status.getEntity().getContent()); + return responseContent; + } + + @Test + public void testAllowAll() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny("Rule 1").read().resourcesOfType(Patient.class).withAnyId().andThen() + .allowAll("Default Rule") + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by rule: Rule 1")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$validate"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + /** + * #528 + */ + @Test + public void testAllowByCompartmentWithAnyType() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder().allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().denyAll().build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "845bd9f1-3635-4866-a6c8-1ca085df5c1a")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "FOO")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + /** + * #528 + */ + @Test + public void testAllowByCompartmentWithType() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder().allow("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().denyAll() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "845bd9f1-3635-4866-a6c8-1ca085df5c1a")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "FOO")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testBatchWhenOnlyTransactionAllowed() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .build(); + } + }); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.BATCH); + input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(Bundle.HTTPVerb.POST); + + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry().getResponse().setLocation("/Patient/1"); + + HttpPost httpPost; + HttpResponse status; + + ourReturn = Collections.singletonList((Resource) output); + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + status = ourClient.execute(httpPost); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + } + + @Test + public void testBatchWhenTransactionReadDenied() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .build(); + } + }); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.BATCH); + input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(Bundle.HTTPVerb.POST); + + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry().setResource(createPatient(2)); + + HttpPost httpPost; + HttpResponse status; + + ourReturn = Collections.singletonList((Resource) output); + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + status = ourClient.execute(httpPost); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + } + + @Test + public void testBatchWhenTransactionWrongBundleType() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .build(); + } + }); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.COLLECTION); + input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(Bundle.HTTPVerb.POST); + + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry().setResource(createPatient(1)); + + HttpPost httpPost; + HttpResponse status; + String response; + + ourReturn = Collections.singletonList((Resource) output); + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + } + + @Test + public void testDeleteByCompartment() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").delete().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").delete().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpDelete httpDelete; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2"); + status = ourClient.execute(httpDelete); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(1)); + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpDelete); + extractResponseAndClose(status); + assertEquals(204, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testDenyAll() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().read().resourcesOfType(Patient.class).withAnyId().andThen() + .denyAll("Default Rule") + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by rule: Default Rule")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$validate"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by rule: Default Rule")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by rule: Default Rule")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + /** + * #528 + */ + @Test + public void testDenyByCompartmentWithAnyType() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder().deny("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().allowAll().build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "845bd9f1-3635-4866-a6c8-1ca085df5c1a")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "FOO")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + /** + * #528 + */ + @Test + public void testDenyByCompartmentWithType() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder().deny("Rule 1").read().resourcesOfType(CarePlan.class).inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().allowAll() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "845bd9f1-3635-4866-a6c8-1ca085df5c1a")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createCarePlan(10, "FOO")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/135154"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testHistoryWithReadAll() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().allResources().withAnyId() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(2, 1)); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/_history"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testMetadataAllow() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").metadata() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(2)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/metadata"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + } + + @Test + public void testMetadataDeny() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.ALLOW) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .deny("Rule 1").metadata() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(2)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/metadata"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + } + + @Test + public void testOperationAnyName() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().withAnyName().onServer().andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + String response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testOperationByInstanceOfTypeAllowed() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourReturn = new ArrayList<>(); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Bundle")); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals(true, ourHitMethod); + + ourReturn = new ArrayList<>(); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Encounter/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("OperationOutcome")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(false, ourHitMethod); + + } + + @Test + public void testOperationByInstanceOfTypeWithInvalidReturnValue() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andThen() + .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // With a return value we don't allow + ourReturn = Collections.singletonList(createPatient(222)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("OperationOutcome")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(true, ourHitMethod); + + // With a return value we do + ourReturn = Collections.singletonList(createPatient(1)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Bundle")); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals(true, ourHitMethod); + + } + + @Test + public void testOperationByInstanceOfTypeWithReturnValue() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourReturn = new ArrayList<>(); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Bundle")); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertEquals(true, ourHitMethod); + + ourReturn = new ArrayList<>(); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Encounter/1/$everything"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("OperationOutcome")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(false, ourHitMethod); + } + + @Test + public void testOperationInstanceLevel() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().named("opName").onInstance(new IdType("http://example.com/Patient/1/_history/2")).andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Wrong instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + } + + @Test + public void testOperationInstanceLevelAnyInstance() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().named("opName").onAnyInstance().andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Another Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/2/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Wrong name + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$opName2"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + } + + @Test + public void testOperationNotAllowedWithWritePermissiom() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").write().allResources().withAnyId().andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // System + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testOperationServerLevel() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().named("opName").onServer().andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testOperationTypeLevel() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().named("opName").onType(Patient.class).andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Wrong type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Wrong name + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName2"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testOperationTypeLevelWildcard() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("RULE 1").operation().named("opName").onAnyType().andThen() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + // Server + ourHitMethod = false; + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Another type + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Wrong name + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName2"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + // Instance + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testReadByAnyId() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).withAnyId() + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourReturn = Collections.singletonList(createPatient(2)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createPatient(2)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/222"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testReadByCompartmentRight() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(1)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createObservation(10, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + status = ourClient.execute(httpGet); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testReadPageRight() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String respString; + Bundle respBundle; + + ourReturn = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + ourReturn.add(createPatient(1)); + } + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNotNull(respBundle.getLink("next")); + + // Load next page + + ourHitMethod = false; + httpGet = new HttpGet(respBundle.getLink("next").getUrl()); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNull(respBundle.getLink("next")); + + } + + @Test + public void testReadPageWrong() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String respString; + Bundle respBundle; + + ourReturn = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ourReturn.add(createPatient(1)); + } + for (int i = 0; i < 5; i++) { + ourReturn.add(createPatient(2)); + } + + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_count=5&_format=json"); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + respBundle = ourCtx.newJsonParser().parseResource(Bundle.class, respString); + assertEquals(5, respBundle.getEntry().size()); + assertEquals(10, respBundle.getTotal()); + assertEquals("Patient/1", respBundle.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless().getValue()); + assertNotNull(respBundle.getLink("next")); + + // Load next page + + ourHitMethod = false; + httpGet = new HttpGet(respBundle.getLink("next").getUrl()); + status = ourClient.execute(httpGet); + respString = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + } + + @Test + public void testReadByCompartmentWrong() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpGet httpGet; + HttpResponse status; + String response; + + ourReturn = Collections.singletonList(createPatient(2)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createObservation(10, "Patient/2")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createCarePlan(10, "Patient/2")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/CarePlan/10"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + ourLog.info(response); + assertThat(response, containsString("Access denied by default policy (no applicable rules)")); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testTransactionWriteGood() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen() + .allow("Rule 2").write().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() + .build(); + } + }); + + Bundle input = new Bundle(); + input.setType(Bundle.BundleType.TRANSACTION); + input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(Bundle.HTTPVerb.PUT); + + Bundle output = new Bundle(); + output.setType(Bundle.BundleType.TRANSACTIONRESPONSE); + output.addEntry().getResponse().setLocation("/Patient/1"); + + HttpPost httpPost; + HttpResponse status; + + ourReturn = Collections.singletonList((Resource) output); + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(createFhirResourceEntity(input)); + status = ourClient.execute(httpPost); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + } + + @Test + public void testWriteByCompartmentCreate() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 1b").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1123")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + // Conditional + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.addHeader("If-None-Exist", "Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation"); + httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/2"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation"); + httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(201, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testWriteByCompartmentCreateConditionalResolvesToValid() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").createConditional().resourcesOfType(Patient.class) + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.addHeader(Constants.HEADER_IF_NONE_EXIST, "foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + String response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(201, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testWriteByCompartmentDeleteConditionalResolvesToValid() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").delete().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").deleteConditional().resourcesOfType(Patient.class) + .build(); + } + }); + + HttpDelete httpDelete; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(1)); + + ourHitMethod = false; + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar"); + status = ourClient.execute(httpDelete); + String response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(204, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testWriteByCompartmentDeleteConditionalWithoutDirectMatch() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 2").deleteConditional().resourcesOfType(Patient.class) + .build(); + } + }); + + HttpDelete httpDelete; + HttpResponse status; + + ourReturn = Collections.singletonList(createPatient(1)); + + ourHitMethod = false; + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient?foo=bar"); + status = ourClient.execute(httpDelete); + String response = extractResponseAndClose(status); + ourLog.info(response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + } + + @Test + public void testWriteByCompartmentDoesntAllowDelete() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpDelete httpDelete; + HttpResponse status; + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(2)); + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2"); + status = ourClient.execute(httpDelete); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + ourReturn = Collections.singletonList(createPatient(1)); + httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/1"); + status = ourClient.execute(httpDelete); + extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + } + + @Test + public void testWriteByCompartmentUpdate() throws Exception { + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")) + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + String response; + HttpResponse status; + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/2"); + httpPost.setEntity(createFhirResourceEntity(createPatient(2))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/1"); + httpPost.setEntity(createFhirResourceEntity(createPatient(1))); + status = ourClient.execute(httpPost); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + // Conditional + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(1))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(99))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/10"); + httpPost.setEntity(createFhirResourceEntity(createObservation(10, "Patient/1"))); + status = ourClient.execute(httpPost); + extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/10"); + httpPost.setEntity(createFhirResourceEntity(createObservation(10, "Patient/2"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(ERR403, response); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testWriteByCompartmentUpdateConditionalResolvesToInvalid() throws Exception { + ourConditionalCreateId = "1123"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 3").updateConditional().resourcesOfType(Patient.class) + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertTrue(ourHitMethod); + + } + + @Test + public void testWriteByCompartmentUpdateConditionalResolvesToValid() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 3").updateConditional().resourcesOfType(Patient.class) + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/12"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + } + + @Test + public void testWriteByCompartmentUpdateConditionalResolvesToValidAllTypes() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().resourcesOfType(Patient.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 2").write().resourcesOfType(Observation.class).inCompartment("Patient", new IdType("Patient/1")).andThen() + .allow("Rule 3").updateConditional().allResources() + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation?foo=bar"); + httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/12"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertTrue(ourHitMethod); + + } + + @Test + public void testInvalidInstanceIds() throws Exception { + try { + new RuleBuilder().allow("Rule 1").write().instance((String) null); + fail(); + } catch (NullPointerException e) { + assertEquals("theId must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(""); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("theId must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance("Observation/"); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("theId must contain an ID part", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType()); + fail(); + } catch (NullPointerException e) { + assertEquals("theId.getValue() must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType("")); + fail(); + } catch (NullPointerException e) { + assertEquals("theId.getValue() must not be null or empty", e.getMessage()); + } + try { + new RuleBuilder().allow("Rule 1").write().instance(new IdType("Observation", (String) null)); + fail(); + } catch (NullPointerException e) { + assertEquals("theId must contain an ID part", e.getMessage()); + } + } + + @Test + public void testWritePatchByInstance() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().instance("Patient/900").andThen() + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]"; + + ourHitMethod = false; + httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900"); + httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(204, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999"); + httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertFalse(ourHitMethod); + } + + @Test + public void testWriteByInstance() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").write().instance("Observation/900").andThen() + .allow("Rule 1").write().instance("901").andThen() + .build(); + } + }); + + HttpEntityEnclosingRequestBase httpPost; + HttpResponse status; + String response; + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/900"); + httpPost.setEntity(createFhirResourceEntity(createObservation(900, "Patient/12"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/901"); + httpPost.setEntity(createFhirResourceEntity(createObservation(901, "Patient/12"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation"); + httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/900"))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + ourHitMethod = false; + httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + httpPost.setEntity(createFhirResourceEntity(createPatient(null))); + status = ourClient.execute(httpPost); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + } + + @Test + public void testReadByInstance() throws Exception { + ourConditionalCreateId = "1"; + + ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow("Rule 1").read().instance("Observation/900").andThen() + .allow("Rule 1").read().instance("901").andThen() + .build(); + } + }); + + HttpResponse status; + String response; + HttpGet httpGet; + + ourReturn = Collections.singletonList(createObservation(900, "Patient/1")); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createPatient(901)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertTrue(ourHitMethod); + + ourReturn = Collections.singletonList(createPatient(1)); + ourHitMethod = false; + httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_format=json"); + status = ourClient.execute(httpGet); + response = extractResponseAndClose(status); + assertEquals(403, status.getStatusLine().getStatusCode()); + assertEquals(ERR403, response); + assertFalse(ourHitMethod); + + } + + @AfterClass + public static void afterClassClearContext() throws Exception { + ourServer.stop(); + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + + ourPort = PortUtil.findFreePort(); + ourServer = new Server(ourPort); + + DummyPatientResourceProvider patProvider = new DummyPatientResourceProvider(); + DummyObservationResourceProvider obsProv = new DummyObservationResourceProvider(); + DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider(); + DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider(); + PlainProvider plainProvider = new PlainProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + ourServlet = new RestfulServer(ourCtx); + ourServlet.setFhirContext(ourCtx); + ourServlet.setResourceProviders(patProvider, obsProv, encProv, cpProv); + ourServlet.setPlainProviders(plainProvider); + ourServlet.setPagingProvider(new FifoMemoryPagingProvider(100)); + ServletHolder servletHolder = new ServletHolder(ourServlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + ourServer.setHandler(proxyHandler); + ourServer.start(); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setConnectionManager(connectionManager); + ourClient = builder.build(); + + } + + public static class DummyCarePlanResourceProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return CarePlan.class; + } + + @Read(version = true) + public CarePlan read(@IdParam IdType theId) { + ourHitMethod = true; + return (CarePlan) ourReturn.get(0); + } + + @Search() + public List search() { + ourHitMethod = true; + return ourReturn; + } + } + + public static class DummyEncounterResourceProvider implements IResourceProvider { + + @Operation(name = "everything", idempotent = true) + public Bundle everything(@IdParam IdType theId) { + ourHitMethod = true; + Bundle retVal = new Bundle(); + for (Resource next : ourReturn) { + retVal.addEntry().setResource(next); + } + return retVal; + } + + @Override + public Class getResourceType() { + return Encounter.class; + } + } + + public static class DummyObservationResourceProvider implements IResourceProvider { + + @Create() + public MethodOutcome create(@ResourceParam Observation theResource, @ConditionalUrlParam String theConditionalUrl) { + ourHitMethod = true; + theResource.setId("Observation/1/_history/1"); + MethodOutcome retVal = new MethodOutcome(); + retVal.setCreated(true); + retVal.setResource(theResource); + return retVal; + } + + @Delete() + public MethodOutcome delete(@IdParam IdType theId) { + ourHitMethod = true; + MethodOutcome retVal = new MethodOutcome(); + return retVal; + } + + @Override + public Class getResourceType() { + return Observation.class; + } + + @Operation(name = "opName", idempotent = true) + public Parameters operation() { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Operation(name = "opName", idempotent = true) + public Parameters operation(@IdParam IdType theId) { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Read(version = true) + public Observation read(@IdParam IdType theId) { + ourHitMethod = true; + return (Observation) ourReturn.get(0); + } + + @Search() + public List search() { + ourHitMethod = true; + return ourReturn; + } + + @Update() + public MethodOutcome update(@IdParam IdType theId, @ResourceParam Observation theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { + ourHitMethod = true; + + if (isNotBlank(theConditionalUrl)) { + IdType actual = new IdType("Observation", ourConditionalCreateId); + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, actual); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + theResource.setId(actual); + } else { + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, theResource); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + theResource.setId(theId.withVersion("2")); + } + + MethodOutcome retVal = new MethodOutcome(); + retVal.setResource(theResource); + return retVal; + } + + } + + public static class DummyPatientResourceProvider implements IResourceProvider { + + @Create() + public MethodOutcome create(@ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { + + if (isNotBlank(theConditionalUrl)) { + IdType actual = new IdType("Patient", ourConditionalCreateId); + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, actual); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.CREATE); + } else { + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, theResource); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.CREATE); + } + + ourHitMethod = true; + theResource.setId("Patient/1/_history/1"); + MethodOutcome retVal = new MethodOutcome(); + retVal.setCreated(true); + retVal.setResource(theResource); + return retVal; + } + + @Delete() + public MethodOutcome delete(IRequestOperationCallback theRequestOperationCallback, @IdParam IdType theId, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { + ourHitMethod = true; + for (IBaseResource next : ourReturn) { + theRequestOperationCallback.resourceDeleted(next); + } + MethodOutcome retVal = new MethodOutcome(); + return retVal; + } + + @Operation(name = "everything", idempotent = true) + public Bundle everything(@IdParam IdType theId) { + ourHitMethod = true; + Bundle retVal = new Bundle(); + for (Resource next : ourReturn) { + retVal.addEntry().setResource(next); + } + return retVal; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @History() + public List history() { + ourHitMethod = true; + return (ourReturn); + } + + @History() + public List history(@IdParam IdType theId) { + ourHitMethod = true; + return (ourReturn); + } + + @Operation(name = "opName", idempotent = true) + public Parameters operation() { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Operation(name = "opName", idempotent = true) + public Parameters operation(@IdParam IdType theId) { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Operation(name = "opName2", idempotent = true) + public Parameters operation2(@IdParam IdType theId) { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Operation(name = "opName2", idempotent = true) + public Parameters operation2() { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Read(version = true) + public Patient read(@IdParam IdType theId) { + ourHitMethod = true; + return (Patient) ourReturn.get(0); + } + + @Search() + public List search() { + ourHitMethod = true; + return ourReturn; + } + + @Update() + public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { + ourHitMethod = true; + + if (isNotBlank(theConditionalUrl)) { + IdType actual = new IdType("Patient", ourConditionalCreateId); + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, actual); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + theResource.setId(actual); + } else { + ActionRequestDetails subRequest = new ActionRequestDetails(theRequestDetails, theResource); + subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE); + theResource.setId(theId.withVersion("2")); + } + + MethodOutcome retVal = new MethodOutcome(); + retVal.setResource(theResource); + return retVal; + } + + @Patch() + public MethodOutcome patch(@IdParam IdType theId, @ResourceParam String theResource, PatchTypeEnum thePatchType) { + ourHitMethod = true; + + MethodOutcome retVal = new MethodOutcome(); + return retVal; + } + + @Validate + public MethodOutcome validate(@ResourceParam Patient theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, + @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile, RequestDetails theRequestDetails) { + ourHitMethod = true; + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setDiagnostics("OK"); + return new MethodOutcome(oo); + } + + @Validate + public MethodOutcome validate(@ResourceParam Patient theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode, + @Validate.Profile String theProfile, RequestDetails theRequestDetails) { + ourHitMethod = true; + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setDiagnostics("OK"); + return new MethodOutcome(oo); + } + + } + + public static class PlainProvider { + + @History() + public List history() { + ourHitMethod = true; + return (ourReturn); + } + + @Operation(name = "opName", idempotent = true) + public Parameters operation() { + ourHitMethod = true; + return (Parameters) new Parameters().setId("1"); + } + + @Transaction() + public Bundle search(@TransactionParam Bundle theInput) { + ourHitMethod = true; + return (Bundle) ourReturn.get(0); + } + + } + +} From d1f8bce4173e48a2391c2dc1a084cce962160be9 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 20 Nov 2017 12:01:24 -0500 Subject: [PATCH 23/24] Add test for #794 --- .../main/java/ca/uhn/fhir/util/TestUtil.java | 23 +++ .../fhir/context/FhirContextDstu3Test.java | 159 ++++++++++-------- 2 files changed, 116 insertions(+), 66 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java index 3e1347dafcb..be799f19e6e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java @@ -30,8 +30,10 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicInteger; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -123,6 +125,27 @@ public class TestUtil { ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID()); } + + /** + * THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE + *

+ * Strip \r chars from a string to account for line ending platform differences + */ + public static void waitForSize(int theTarget, AtomicInteger theInteger) { + long start = System.currentTimeMillis(); + while (theInteger.get() != theTarget && (System.currentTimeMillis() - start) <= 15000) { + try { + Thread.sleep(50); + } catch (InterruptedException theE) { + throw new Error(theE); + } + } + if ((System.currentTimeMillis() - start) >= 15000) { + throw new IllegalStateException("Size " + theInteger.get() + " is != target " + theTarget); + } + } + + /** * THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE *

diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java index 7f7ed60152e..abf39e1c0df 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java @@ -1,13 +1,8 @@ package ca.uhn.fhir.context; -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import ca.uhn.fhir.rest.client.MyPatientWithExtensions; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Reference; @@ -15,19 +10,21 @@ import org.hl7.fhir.dstu3.model.StructureDefinition; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.rest.client.MyPatientWithExtensions; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; public class FhirContextDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContextDstu3Test.class); private static FhirContext ourCtx = FhirContext.forDstu3(); - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - + @SuppressWarnings("deprecation") @Test public void testAutoDetectVersion() { @@ -35,6 +32,17 @@ public class FhirContextDstu3Test { assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion()); } + @Test + public void testCustomTypeDoesntBecomeDefault() { + FhirContext ctx = FhirContext.forDstu3(); + + MyPatientWithExtensions pt = new MyPatientWithExtensions(); + pt.addName().addGiven("FOO"); + ctx.newXmlParser().encodeResourceToString(pt); + + assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass()); + } + /** * See #344 */ @@ -44,7 +52,7 @@ public class FhirContextDstu3Test { assertEquals(Reference.class, ourCtx.getElementDefinition("Reference").getImplementingClass()); assertEquals(Reference.class, ourCtx.getElementDefinition("REFerence").getImplementingClass()); } - + /** * See #344 */ @@ -57,73 +65,92 @@ public class FhirContextDstu3Test { } @Test - public void testCustomTypeDoesntBecomeDefault() { - FhirContext ctx = FhirContext.forDstu3(); - - MyPatientWithExtensions pt = new MyPatientWithExtensions(); - pt.addName().addGiven("FOO"); - ctx.newXmlParser().encodeResourceToString(pt); - - assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass()); + public void testInitialisationThreadSafety() { + final FhirContext ctx = FhirContext.forDstu3(); + + final int numThreads = 40; + final List exceptions = Collections.synchronizedList(new ArrayList()); + final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads); + try { + final CountDownLatch threadsReady = new CountDownLatch(numThreads); + final CountDownLatch threadsFinished = new CountDownLatch(numThreads); + + for (int i = 0; i < numThreads; i++) { + threadPool.submit( + new Runnable() { + public void run() { + threadsReady.countDown(); + try { + threadsReady.await(); + RuntimeResourceDefinition def = ctx.getResourceDefinition("patient"); + ourLog.info(def.toString()); + assertNotNull(def); + } catch (final Exception e) { + exceptions.add(e); + } + threadsFinished.countDown(); + } + } + ); + } + + threadsFinished.await(); + } catch (final InterruptedException e) { + exceptions.add(e); + } finally { + threadPool.shutdownNow(); + } + + assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty()); } - + + /** + * See #794 + */ + @Test + public void testInitializeThreadSafety2() throws InterruptedException { + final FhirContext dstu3FhirContext = FhirContext.forDstu3(); + + final AtomicInteger count = new AtomicInteger(); + + for (int i = 0; i < 10; i++) { + new Thread(new Runnable() { + @Override + public void run() { + OperationOutcomeUtil.newInstance(dstu3FhirContext); + ourLog.info("Have finished {}", count.incrementAndGet()); + } + }).start(); + } + + TestUtil.waitForSize(10, count); + + } + @Test public void testQueryBoundCode() { RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); String childName = "gender"; BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); ourLog.trace(genderChild.getClass().getName()); - + assertEquals(AdministrativeGender.class, genderChild.getBoundEnumType()); } - + @Test public void testQueryNonBoundCode() { RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); String childName = "name"; BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); ourLog.trace(genderChild.getClass().getName()); - + assertEquals(null, genderChild.getBoundEnumType()); } - @Test - public void testInitialisationThreadSafety() { - final FhirContext ctx = FhirContext.forDstu3(); + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } - final int numThreads = 40; - final List exceptions = Collections.synchronizedList(new ArrayList()); - final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads); - try { - final CountDownLatch threadsReady = new CountDownLatch(numThreads); - final CountDownLatch threadsFinished = new CountDownLatch(numThreads); - for (int i = 0; i < numThreads; i++) { - threadPool.submit( - new Runnable() { - public void run() { - threadsReady.countDown(); - try { - threadsReady.await(); - RuntimeResourceDefinition def = ctx.getResourceDefinition("patient"); - ourLog.info(def.toString()); - assertNotNull(def); - } catch(final Exception e) { - exceptions.add(e); - } - threadsFinished.countDown(); - } - } - ); - } - - threadsFinished.await(); - } catch(final InterruptedException e) { - exceptions.add(e); - } finally { - threadPool.shutdownNow(); - } - - assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty()); - } } From 60b6c3745b0ee1a17388807f12faf0c0c08ba261 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 20 Nov 2017 12:01:24 -0500 Subject: [PATCH 24/24] Add test for #794 --- .../main/java/ca/uhn/fhir/util/TestUtil.java | 23 +++ .../fhir/context/FhirContextDstu3Test.java | 159 ++++++++++-------- 2 files changed, 116 insertions(+), 66 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java index 3e1347dafcb..6ef1f64541f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TestUtil.java @@ -30,8 +30,10 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicInteger; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -123,6 +125,27 @@ public class TestUtil { ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID()); } + + /** + * THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE + *

+ * Wait for an atomicinteger to hit a given site and fail if it never does + */ + public static void waitForSize(int theTarget, AtomicInteger theInteger) { + long start = System.currentTimeMillis(); + while (theInteger.get() != theTarget && (System.currentTimeMillis() - start) <= 15000) { + try { + Thread.sleep(50); + } catch (InterruptedException theE) { + throw new Error(theE); + } + } + if ((System.currentTimeMillis() - start) >= 15000) { + throw new IllegalStateException("Size " + theInteger.get() + " is != target " + theTarget); + } + } + + /** * THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE *

diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java index 7f7ed60152e..abf39e1c0df 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/FhirContextDstu3Test.java @@ -1,13 +1,8 @@ package ca.uhn.fhir.context; -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import ca.uhn.fhir.rest.client.MyPatientWithExtensions; +import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Reference; @@ -15,19 +10,21 @@ import org.hl7.fhir.dstu3.model.StructureDefinition; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.rest.client.MyPatientWithExtensions; -import ca.uhn.fhir.util.TestUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; public class FhirContextDstu3Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContextDstu3Test.class); private static FhirContext ourCtx = FhirContext.forDstu3(); - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - + @SuppressWarnings("deprecation") @Test public void testAutoDetectVersion() { @@ -35,6 +32,17 @@ public class FhirContextDstu3Test { assertEquals(FhirVersionEnum.DSTU3, ctx.getVersion().getVersion()); } + @Test + public void testCustomTypeDoesntBecomeDefault() { + FhirContext ctx = FhirContext.forDstu3(); + + MyPatientWithExtensions pt = new MyPatientWithExtensions(); + pt.addName().addGiven("FOO"); + ctx.newXmlParser().encodeResourceToString(pt); + + assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass()); + } + /** * See #344 */ @@ -44,7 +52,7 @@ public class FhirContextDstu3Test { assertEquals(Reference.class, ourCtx.getElementDefinition("Reference").getImplementingClass()); assertEquals(Reference.class, ourCtx.getElementDefinition("REFerence").getImplementingClass()); } - + /** * See #344 */ @@ -57,73 +65,92 @@ public class FhirContextDstu3Test { } @Test - public void testCustomTypeDoesntBecomeDefault() { - FhirContext ctx = FhirContext.forDstu3(); - - MyPatientWithExtensions pt = new MyPatientWithExtensions(); - pt.addName().addGiven("FOO"); - ctx.newXmlParser().encodeResourceToString(pt); - - assertEquals(Patient.class, ctx.getResourceDefinition("Patient").getImplementingClass()); + public void testInitialisationThreadSafety() { + final FhirContext ctx = FhirContext.forDstu3(); + + final int numThreads = 40; + final List exceptions = Collections.synchronizedList(new ArrayList()); + final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads); + try { + final CountDownLatch threadsReady = new CountDownLatch(numThreads); + final CountDownLatch threadsFinished = new CountDownLatch(numThreads); + + for (int i = 0; i < numThreads; i++) { + threadPool.submit( + new Runnable() { + public void run() { + threadsReady.countDown(); + try { + threadsReady.await(); + RuntimeResourceDefinition def = ctx.getResourceDefinition("patient"); + ourLog.info(def.toString()); + assertNotNull(def); + } catch (final Exception e) { + exceptions.add(e); + } + threadsFinished.countDown(); + } + } + ); + } + + threadsFinished.await(); + } catch (final InterruptedException e) { + exceptions.add(e); + } finally { + threadPool.shutdownNow(); + } + + assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty()); } - + + /** + * See #794 + */ + @Test + public void testInitializeThreadSafety2() throws InterruptedException { + final FhirContext dstu3FhirContext = FhirContext.forDstu3(); + + final AtomicInteger count = new AtomicInteger(); + + for (int i = 0; i < 10; i++) { + new Thread(new Runnable() { + @Override + public void run() { + OperationOutcomeUtil.newInstance(dstu3FhirContext); + ourLog.info("Have finished {}", count.incrementAndGet()); + } + }).start(); + } + + TestUtil.waitForSize(10, count); + + } + @Test public void testQueryBoundCode() { RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); String childName = "gender"; BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); ourLog.trace(genderChild.getClass().getName()); - + assertEquals(AdministrativeGender.class, genderChild.getBoundEnumType()); } - + @Test public void testQueryNonBoundCode() { RuntimeResourceDefinition patientType = ourCtx.getResourceDefinition(Patient.class); String childName = "name"; BaseRuntimeChildDatatypeDefinition genderChild = (BaseRuntimeChildDatatypeDefinition) patientType.getChildByName(childName); ourLog.trace(genderChild.getClass().getName()); - + assertEquals(null, genderChild.getBoundEnumType()); } - @Test - public void testInitialisationThreadSafety() { - final FhirContext ctx = FhirContext.forDstu3(); + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } - final int numThreads = 40; - final List exceptions = Collections.synchronizedList(new ArrayList()); - final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads); - try { - final CountDownLatch threadsReady = new CountDownLatch(numThreads); - final CountDownLatch threadsFinished = new CountDownLatch(numThreads); - for (int i = 0; i < numThreads; i++) { - threadPool.submit( - new Runnable() { - public void run() { - threadsReady.countDown(); - try { - threadsReady.await(); - RuntimeResourceDefinition def = ctx.getResourceDefinition("patient"); - ourLog.info(def.toString()); - assertNotNull(def); - } catch(final Exception e) { - exceptions.add(e); - } - threadsFinished.countDown(); - } - } - ); - } - - threadsFinished.await(); - } catch(final InterruptedException e) { - exceptions.add(e); - } finally { - threadPool.shutdownNow(); - } - - assertTrue("failed with exception(s): " + exceptions, exceptions.isEmpty()); - } }