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}