001package ca.uhn.fhir.spring.boot.autoconfigure; 002 003/*- 004 * #%L 005 * hapi-fhir-spring-boot-autoconfigure 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import ca.uhn.fhir.context.FhirContext; 025import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; 026import ca.uhn.fhir.jpa.api.config.DaoConfig; 027import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; 028import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; 029import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; 030import ca.uhn.fhir.jpa.model.config.PartitionSettings; 031import ca.uhn.fhir.jpa.model.entity.ModelConfig; 032import ca.uhn.fhir.jpa.provider.BaseJpaProvider; 033import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider; 034import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; 035import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; 036import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; 037import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; 038import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; 039import ca.uhn.fhir.rest.client.api.IClientInterceptor; 040import ca.uhn.fhir.rest.client.api.IGenericClient; 041import ca.uhn.fhir.rest.client.api.IRestfulClientFactory; 042import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; 043import ca.uhn.fhir.rest.server.IPagingProvider; 044import ca.uhn.fhir.rest.server.IResourceProvider; 045import ca.uhn.fhir.rest.server.RestfulServer; 046import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; 047import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; 048import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; 049import okhttp3.OkHttpClient; 050import org.apache.http.client.HttpClient; 051import org.springframework.beans.factory.ObjectProvider; 052import org.springframework.beans.factory.annotation.Autowired; 053import org.springframework.boot.autoconfigure.AutoConfigureAfter; 054import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 055import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 056import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 057import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 058import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 059import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 060import org.springframework.boot.autoconfigure.condition.ResourceCondition; 061import org.springframework.boot.autoconfigure.domain.EntityScan; 062import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 063import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 064import org.springframework.boot.context.properties.ConfigurationProperties; 065import org.springframework.boot.context.properties.EnableConfigurationProperties; 066import org.springframework.boot.web.servlet.ServletRegistrationBean; 067import org.springframework.context.annotation.Bean; 068import org.springframework.context.annotation.Conditional; 069import org.springframework.context.annotation.Configuration; 070import org.springframework.context.annotation.Import; 071import org.springframework.context.annotation.Primary; 072import org.springframework.core.annotation.AnnotationAwareOrderComparator; 073import org.springframework.orm.jpa.JpaTransactionManager; 074import org.springframework.transaction.PlatformTransactionManager; 075import org.springframework.util.CollectionUtils; 076 077import javax.persistence.EntityManagerFactory; 078import javax.servlet.ServletException; 079import javax.sql.DataSource; 080import java.util.List; 081import java.util.concurrent.ScheduledExecutorService; 082 083/** 084 * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. 085 * 086 * @author Mathieu Ouellet 087 */ 088@Configuration 089@AutoConfigureAfter({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) 090@EnableConfigurationProperties(FhirProperties.class) 091public class FhirAutoConfiguration { 092 093 094 private final FhirProperties properties; 095 096 public FhirAutoConfiguration(FhirProperties properties) { 097 this.properties = properties; 098 } 099 100 @Bean 101 @ConditionalOnMissingBean 102 public FhirContext fhirContext() { 103 FhirContext fhirContext = new FhirContext(properties.getVersion()); 104 return fhirContext; 105 } 106 107 108 @Configuration 109 @ConditionalOnClass(AbstractJaxRsProvider.class) 110 @EnableConfigurationProperties(FhirProperties.class) 111 @ConfigurationProperties("hapi.fhir.rest") 112 @SuppressWarnings("serial") 113 static class FhirRestfulServerConfiguration extends RestfulServer { 114 115 private final FhirProperties properties; 116 117 private final FhirContext fhirContext; 118 119 private final List<IResourceProvider> resourceProviders; 120 121 private final IPagingProvider pagingProvider; 122 123 private final List<FhirRestfulServerCustomizer> customizers; 124 125 public FhirRestfulServerConfiguration( 126 FhirProperties properties, 127 FhirContext fhirContext, 128 ObjectProvider<List<IResourceProvider>> resourceProviders, 129 ObjectProvider<IPagingProvider> pagingProvider, 130 ObjectProvider<List<IServerInterceptor>> interceptors, 131 ObjectProvider<List<FhirRestfulServerCustomizer>> customizers) { 132 this.properties = properties; 133 this.fhirContext = fhirContext; 134 this.resourceProviders = resourceProviders.getIfAvailable(); 135 this.pagingProvider = pagingProvider.getIfAvailable(); 136 this.customizers = customizers.getIfAvailable(); 137 } 138 139 private void customize() { 140 if (this.customizers != null) { 141 AnnotationAwareOrderComparator.sort(this.customizers); 142 for (FhirRestfulServerCustomizer customizer : this.customizers) { 143 customizer.customize(this); 144 } 145 } 146 } 147 148 @Bean 149 public ServletRegistrationBean fhirServerRegistrationBean() { 150 ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); 151 registration.setLoadOnStartup(1); 152 return registration; 153 } 154 155 @Override 156 protected void initialize() throws ServletException { 157 super.initialize(); 158 159 setFhirContext(this.fhirContext); 160 setResourceProviders(this.resourceProviders); 161 setPagingProvider(this.pagingProvider); 162 163 setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); 164 165 customize(); 166 } 167 } 168 169 @Configuration 170 @ConditionalOnClass(BaseJpaProvider.class) 171 @ConditionalOnBean(DataSource.class) 172 @EnableConfigurationProperties(FhirProperties.class) 173 static class FhirJpaServerConfiguration { 174 @Autowired 175 private ScheduledExecutorService myScheduledExecutorService; 176 177 @Configuration 178 @EntityScan(basePackages = {"ca.uhn.fhir.jpa.entity", "ca.uhn.fhir.jpa.model.entity"}) 179 @Import({ 180 SubscriptionChannelConfig.class, 181 SubscriptionProcessorConfig.class, 182 SubscriptionSubmitterConfig.class 183 }) 184 static class FhirJpaDaoConfiguration { 185 186 @Autowired 187 private EntityManagerFactory emf; 188 189 @Bean 190 @Primary 191 public PlatformTransactionManager hapiTransactionManager() { 192 return new JpaTransactionManager(emf); 193 } 194 195 @Bean 196 @ConditionalOnMissingBean 197 @ConfigurationProperties("hapi.fhir.jpa") 198 public DaoConfig fhirDaoConfig() { 199 DaoConfig fhirDaoConfig = new DaoConfig(); 200 return fhirDaoConfig; 201 } 202 203 @Bean 204 @ConditionalOnMissingBean 205 @ConfigurationProperties("hapi.fhir.jpa") 206 public PartitionSettings partitionSettings() { 207 return new PartitionSettings(); 208 } 209 210 211 @Bean 212 @ConditionalOnMissingBean 213 @ConfigurationProperties("hapi.fhir.jpa") 214 public ModelConfig fhirModelConfig() { 215 return fhirDaoConfig().getModelConfig(); 216 } 217 } 218 219 @Configuration 220 @ConditionalOnBean({DaoConfig.class, RestfulServer.class}) 221 @SuppressWarnings("rawtypes") 222 static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { 223 224 private final BaseJpaSystemProvider systemProviders; 225 226 public RestfulServerCustomizer(ObjectProvider<BaseJpaSystemProvider> systemProviders) { 227 this.systemProviders = systemProviders.getIfAvailable(); 228 } 229 230 @Override 231 public void customize(RestfulServer server) { 232 server.setPlainProviders(systemProviders); 233 } 234 } 235 236 @Configuration 237 @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") 238 @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") 239 static class Dstu3 extends BaseJavaConfigDstu3 { 240 } 241 242 @Configuration 243 @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") 244 @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") 245 static class Dstu2 extends BaseJavaConfigDstu2 { 246 } 247 248 @Configuration 249 @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") 250 @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "R4") 251 static class R4 extends BaseJavaConfigR4 { 252 } 253 } 254 255 @Configuration 256 @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) 257 @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) 258 static class FhirValidationConfiguration { 259 260 @Bean 261 @ConditionalOnMissingBean 262 public RequestValidatingInterceptor requestValidatingInterceptor() { 263 return new RequestValidatingInterceptor(); 264 } 265 266 @Bean 267 @ConditionalOnMissingBean 268 @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") 269 public ResponseValidatingInterceptor responseValidatingInterceptor() { 270 return new ResponseValidatingInterceptor(); 271 } 272 273 static class SchemaAvailableCondition extends ResourceCondition { 274 275 SchemaAvailableCondition() { 276 super("ValidationSchema", 277 "hapi.fhir.validation", 278 "schema-location", 279 "classpath:/org/hl7/fhir/instance/model/schema", 280 "classpath:/org/hl7/fhir/dstu2016may/model/schema", 281 "classpath:/org/hl7/fhir/dstu3/model/schema"); 282 } 283 } 284 } 285 286 @Configuration 287 @ConditionalOnProperty("hapi.fhir.server.url") 288 @EnableConfigurationProperties(FhirProperties.class) 289 static class FhirRestfulClientConfiguration { 290 291 private final FhirProperties properties; 292 293 private final List<IClientInterceptor> clientInterceptors; 294 295 public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider<List<IClientInterceptor>> clientInterceptors) { 296 this.properties = properties; 297 this.clientInterceptors = clientInterceptors.getIfAvailable(); 298 } 299 300 @Bean 301 @ConditionalOnBean(IRestfulClientFactory.class) 302 public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { 303 IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); 304 if (!CollectionUtils.isEmpty(this.clientInterceptors)) { 305 for (IClientInterceptor interceptor : this.clientInterceptors) { 306 fhirClient.registerInterceptor(interceptor); 307 } 308 } 309 return fhirClient; 310 } 311 312 @Configuration 313 @ConditionalOnClass(HttpClient.class) 314 @ConditionalOnMissingClass("okhttp3.OkHttpClient") 315 static class Apache { 316 317 private final FhirContext context; 318 319 public Apache(FhirContext context) { 320 this.context = context; 321 } 322 323 @Bean 324 @ConditionalOnMissingBean 325 @ConfigurationProperties("hapi.fhir.rest.client.apache") 326 public IRestfulClientFactory fhirRestfulClientFactory() { 327 ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); 328 return restfulClientFactory; 329 } 330 } 331 332 @Configuration 333 @ConditionalOnClass(OkHttpClient.class) 334 static class OkHttp { 335 336 private final FhirContext context; 337 338 public OkHttp(FhirContext context) { 339 this.context = context; 340 } 341 342 @Bean 343 @ConditionalOnMissingBean 344 @ConfigurationProperties("hapi.fhir.rest.client.okhttp") 345 public IRestfulClientFactory fhirRestfulClientFactory() { 346 OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); 347 return restfulClientFactory; 348 } 349 } 350 } 351 352}