001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.orm.jpa;
018
019import java.util.List;
020import java.util.Map;
021
022import javax.persistence.EntityManagerFactory;
023import javax.sql.DataSource;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import org.springframework.beans.BeansException;
029import org.springframework.beans.factory.BeanFactory;
030import org.springframework.beans.factory.BeanFactoryAware;
031import org.springframework.beans.factory.ObjectProvider;
032import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
033import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
034import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
035import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
036import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
037import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
038import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
039import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
040import org.springframework.boot.autoconfigure.domain.EntityScanPackages;
041import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
042import org.springframework.boot.context.properties.EnableConfigurationProperties;
043import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
044import org.springframework.context.annotation.Bean;
045import org.springframework.context.annotation.Configuration;
046import org.springframework.context.annotation.Import;
047import org.springframework.context.annotation.Primary;
048import org.springframework.orm.jpa.JpaTransactionManager;
049import org.springframework.orm.jpa.JpaVendorAdapter;
050import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
051import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
052import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
053import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
054import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
055import org.springframework.transaction.PlatformTransactionManager;
056import org.springframework.transaction.jta.JtaTransactionManager;
057import org.springframework.util.ObjectUtils;
058import org.springframework.util.StringUtils;
059import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
060import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
061
062/**
063 * Base {@link EnableAutoConfiguration Auto-configuration} for JPA.
064 *
065 * @author Phillip Webb
066 * @author Dave Syer
067 * @author Oliver Gierke
068 * @author Andy Wilkinson
069 * @author Kazuki Shimizu
070 * @author EddĂș MelĂ©ndez
071 */
072@Configuration
073@EnableConfigurationProperties(JpaProperties.class)
074@Import(DataSourceInitializedPublisher.Registrar.class)
075public abstract class JpaBaseConfiguration implements BeanFactoryAware {
076
077        private final DataSource dataSource;
078
079        private final JpaProperties properties;
080
081        private final JtaTransactionManager jtaTransactionManager;
082
083        private final TransactionManagerCustomizers transactionManagerCustomizers;
084
085        private ConfigurableListableBeanFactory beanFactory;
086
087        protected JpaBaseConfiguration(DataSource dataSource, JpaProperties properties,
088                        ObjectProvider<JtaTransactionManager> jtaTransactionManager,
089                        ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
090                this.dataSource = dataSource;
091                this.properties = properties;
092                this.jtaTransactionManager = jtaTransactionManager.getIfAvailable();
093                this.transactionManagerCustomizers = transactionManagerCustomizers
094                                .getIfAvailable();
095        }
096
097        @Bean
098        @ConditionalOnMissingBean
099        public PlatformTransactionManager transactionManager() {
100                JpaTransactionManager transactionManager = new JpaTransactionManager();
101                if (this.transactionManagerCustomizers != null) {
102                        this.transactionManagerCustomizers.customize(transactionManager);
103                }
104                return transactionManager;
105        }
106
107        @Bean
108        @ConditionalOnMissingBean
109        public JpaVendorAdapter jpaVendorAdapter() {
110                AbstractJpaVendorAdapter adapter = createJpaVendorAdapter();
111                adapter.setShowSql(this.properties.isShowSql());
112                adapter.setDatabase(this.properties.determineDatabase(this.dataSource));
113                adapter.setDatabasePlatform(this.properties.getDatabasePlatform());
114                adapter.setGenerateDdl(this.properties.isGenerateDdl());
115                return adapter;
116        }
117
118        @Bean
119        @ConditionalOnMissingBean
120        public EntityManagerFactoryBuilder entityManagerFactoryBuilder(
121                        JpaVendorAdapter jpaVendorAdapter,
122                        ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
123                        ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers) {
124                EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
125                                jpaVendorAdapter, this.properties.getProperties(),
126                                persistenceUnitManager.getIfAvailable());
127                customizers.orderedStream()
128                                .forEach((customizer) -> customizer.customize(builder));
129                return builder;
130        }
131
132        @Bean
133        @Primary
134        @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class,
135                        EntityManagerFactory.class })
136        public LocalContainerEntityManagerFactoryBean entityManagerFactory(
137                        EntityManagerFactoryBuilder factoryBuilder) {
138                Map<String, Object> vendorProperties = getVendorProperties();
139                customizeVendorProperties(vendorProperties);
140                return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan())
141                                .properties(vendorProperties).mappingResources(getMappingResources())
142                                .jta(isJta()).build();
143        }
144
145        protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
146
147        protected abstract Map<String, Object> getVendorProperties();
148
149        /**
150         * Customize vendor properties before they are used. Allows for post processing (for
151         * example to configure JTA specific settings).
152         * @param vendorProperties the vendor properties to customize
153         */
154        protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
155        }
156
157        protected String[] getPackagesToScan() {
158                List<String> packages = EntityScanPackages.get(this.beanFactory)
159                                .getPackageNames();
160                if (packages.isEmpty() && AutoConfigurationPackages.has(this.beanFactory)) {
161                        packages = AutoConfigurationPackages.get(this.beanFactory);
162                }
163                return StringUtils.toStringArray(packages);
164        }
165
166        private String[] getMappingResources() {
167                List<String> mappingResources = this.properties.getMappingResources();
168                return (!ObjectUtils.isEmpty(mappingResources)
169                                ? StringUtils.toStringArray(mappingResources) : null);
170        }
171
172        /**
173         * Return the JTA transaction manager.
174         * @return the transaction manager or {@code null}
175         */
176        protected JtaTransactionManager getJtaTransactionManager() {
177                return this.jtaTransactionManager;
178        }
179
180        /**
181         * Returns if a JTA {@link PlatformTransactionManager} is being used.
182         * @return if a JTA transaction manager is being used
183         */
184        protected final boolean isJta() {
185                return (this.jtaTransactionManager != null);
186        }
187
188        /**
189         * Return the {@link JpaProperties}.
190         * @return the properties
191         */
192        protected final JpaProperties getProperties() {
193                return this.properties;
194        }
195
196        /**
197         * Return the {@link DataSource}.
198         * @return the data source
199         */
200        protected final DataSource getDataSource() {
201                return this.dataSource;
202        }
203
204        @Override
205        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
206                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
207        }
208
209        @Configuration
210        @ConditionalOnWebApplication(type = Type.SERVLET)
211        @ConditionalOnClass(WebMvcConfigurer.class)
212        @ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class,
213                        OpenEntityManagerInViewFilter.class })
214        @ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
215        protected static class JpaWebConfiguration {
216
217                // Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when
218                // not on the classpath
219                @Configuration
220                protected static class JpaWebMvcConfiguration implements WebMvcConfigurer {
221
222                        private static final Log logger = LogFactory
223                                        .getLog(JpaWebMvcConfiguration.class);
224
225                        private final JpaProperties jpaProperties;
226
227                        protected JpaWebMvcConfiguration(JpaProperties jpaProperties) {
228                                this.jpaProperties = jpaProperties;
229                        }
230
231                        @Bean
232                        public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
233                                if (this.jpaProperties.getOpenInView() == null) {
234                                        logger.warn("spring.jpa.open-in-view is enabled by default. "
235                                                        + "Therefore, database queries may be performed during view "
236                                                        + "rendering. Explicitly configure "
237                                                        + "spring.jpa.open-in-view to disable this warning");
238                                }
239                                return new OpenEntityManagerInViewInterceptor();
240                        }
241
242                        @Override
243                        public void addInterceptors(InterceptorRegistry registry) {
244                                registry.addWebRequestInterceptor(openEntityManagerInViewInterceptor());
245                        }
246
247                }
248
249        }
250
251}