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.orm.jpa;
018
019import java.net.URL;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.LinkedHashMap;
023import java.util.Map;
024import java.util.Set;
025
026import javax.sql.DataSource;
027
028import org.springframework.core.task.AsyncTaskExecutor;
029import org.springframework.orm.jpa.JpaVendorAdapter;
030import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
031import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
032import org.springframework.util.ClassUtils;
033import org.springframework.util.ObjectUtils;
034import org.springframework.util.StringUtils;
035
036/**
037 * Convenient builder for JPA EntityManagerFactory instances. Collects common
038 * configuration when constructed and then allows you to create one or more
039 * {@link LocalContainerEntityManagerFactoryBean} through a fluent builder pattern. The
040 * most common options are covered in the builder, but you can always manipulate the
041 * product of the builder if you need more control, before returning it from a
042 * {@code @Bean} definition.
043 *
044 * @author Dave Syer
045 * @author Phillip Webb
046 * @author Stephane Nicoll
047 * @since 1.3.0
048 */
049public class EntityManagerFactoryBuilder {
050
051        private final JpaVendorAdapter jpaVendorAdapter;
052
053        private final PersistenceUnitManager persistenceUnitManager;
054
055        private final Map<String, Object> jpaProperties;
056
057        private final URL persistenceUnitRootLocation;
058
059        private AsyncTaskExecutor bootstrapExecutor;
060
061        /**
062         * Create a new instance passing in the common pieces that will be shared if multiple
063         * EntityManagerFactory instances are created.
064         * @param jpaVendorAdapter a vendor adapter
065         * @param jpaProperties the JPA properties to be passed to the persistence provider
066         * @param persistenceUnitManager optional source of persistence unit information (can
067         * be null)
068         */
069        public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
070                        Map<String, ?> jpaProperties, PersistenceUnitManager persistenceUnitManager) {
071                this(jpaVendorAdapter, jpaProperties, persistenceUnitManager, null);
072        }
073
074        /**
075         * Create a new instance passing in the common pieces that will be shared if multiple
076         * EntityManagerFactory instances are created.
077         * @param jpaVendorAdapter a vendor adapter
078         * @param jpaProperties the JPA properties to be passed to the persistence provider
079         * @param persistenceUnitManager optional source of persistence unit information (can
080         * be null)
081         * @param persistenceUnitRootLocation the persistence unit root location to use as a
082         * fallback (can be null)
083         * @since 1.4.1
084         */
085        public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
086                        Map<String, ?> jpaProperties, PersistenceUnitManager persistenceUnitManager,
087                        URL persistenceUnitRootLocation) {
088                this.jpaVendorAdapter = jpaVendorAdapter;
089                this.persistenceUnitManager = persistenceUnitManager;
090                this.jpaProperties = new LinkedHashMap<>(jpaProperties);
091                this.persistenceUnitRootLocation = persistenceUnitRootLocation;
092        }
093
094        public Builder dataSource(DataSource dataSource) {
095                return new Builder(dataSource);
096        }
097
098        /**
099         * Configure the bootstrap executor to be used by the
100         * {@link LocalContainerEntityManagerFactoryBean}.
101         * @param bootstrapExecutor the executor
102         * @since 2.1.0
103         */
104        public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
105                this.bootstrapExecutor = bootstrapExecutor;
106        }
107
108        /**
109         * A fluent builder for a LocalContainerEntityManagerFactoryBean.
110         */
111        public final class Builder {
112
113                private DataSource dataSource;
114
115                private String[] packagesToScan;
116
117                private String persistenceUnit;
118
119                private Map<String, Object> properties = new HashMap<>();
120
121                private String[] mappingResources;
122
123                private boolean jta;
124
125                private Builder(DataSource dataSource) {
126                        this.dataSource = dataSource;
127                }
128
129                /**
130                 * The names of packages to scan for {@code @Entity} annotations.
131                 * @param packagesToScan packages to scan
132                 * @return the builder for fluent usage
133                 */
134                public Builder packages(String... packagesToScan) {
135                        this.packagesToScan = packagesToScan;
136                        return this;
137                }
138
139                /**
140                 * The classes whose packages should be scanned for {@code @Entity} annotations.
141                 * @param basePackageClasses the classes to use
142                 * @return the builder for fluent usage
143                 */
144                public Builder packages(Class<?>... basePackageClasses) {
145                        Set<String> packages = new HashSet<>();
146                        for (Class<?> type : basePackageClasses) {
147                                packages.add(ClassUtils.getPackageName(type));
148                        }
149                        this.packagesToScan = StringUtils.toStringArray(packages);
150                        return this;
151                }
152
153                /**
154                 * The name of the persistence unit. If only building one EntityManagerFactory you
155                 * can omit this, but if there are more than one in the same application you
156                 * should give them distinct names.
157                 * @param persistenceUnit the name of the persistence unit
158                 * @return the builder for fluent usage
159                 */
160                public Builder persistenceUnit(String persistenceUnit) {
161                        this.persistenceUnit = persistenceUnit;
162                        return this;
163                }
164
165                /**
166                 * Generic properties for standard JPA or vendor-specific configuration. These
167                 * properties override any values provided in the constructor.
168                 * @param properties the properties to use
169                 * @return the builder for fluent usage
170                 */
171                public Builder properties(Map<String, ?> properties) {
172                        this.properties.putAll(properties);
173                        return this;
174                }
175
176                /**
177                 * The mapping resources (equivalent to {@code <mapping-file>} entries in
178                 * {@code persistence.xml}) for the persistence unit.
179                 * <p>
180                 * Note that mapping resources must be relative to the classpath root, e.g.
181                 * "META-INF/mappings.xml" or "com/mycompany/repository/mappings.xml", so that
182                 * they can be loaded through {@code ClassLoader.getResource()}.
183                 * @param mappingResources the mapping resources to use
184                 * @return the builder for fluent usage
185                 */
186                public Builder mappingResources(String... mappingResources) {
187                        this.mappingResources = mappingResources;
188                        return this;
189                }
190
191                /**
192                 * Configure if using a JTA {@link DataSource}, i.e. if
193                 * {@link LocalContainerEntityManagerFactoryBean#setDataSource(DataSource)
194                 * setDataSource} or
195                 * {@link LocalContainerEntityManagerFactoryBean#setJtaDataSource(DataSource)
196                 * setJtaDataSource} should be called on the
197                 * {@link LocalContainerEntityManagerFactoryBean}.
198                 * @param jta if the data source is JTA
199                 * @return the builder for fluent usage
200                 */
201                public Builder jta(boolean jta) {
202                        this.jta = jta;
203                        return this;
204                }
205
206                public LocalContainerEntityManagerFactoryBean build() {
207                        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
208                        if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) {
209                                entityManagerFactoryBean.setPersistenceUnitManager(
210                                                EntityManagerFactoryBuilder.this.persistenceUnitManager);
211                        }
212                        if (this.persistenceUnit != null) {
213                                entityManagerFactoryBean.setPersistenceUnitName(this.persistenceUnit);
214                        }
215                        entityManagerFactoryBean.setJpaVendorAdapter(
216                                        EntityManagerFactoryBuilder.this.jpaVendorAdapter);
217
218                        if (this.jta) {
219                                entityManagerFactoryBean.setJtaDataSource(this.dataSource);
220                        }
221                        else {
222                                entityManagerFactoryBean.setDataSource(this.dataSource);
223                        }
224                        entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
225                        entityManagerFactoryBean.getJpaPropertyMap()
226                                        .putAll(EntityManagerFactoryBuilder.this.jpaProperties);
227                        entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
228                        if (!ObjectUtils.isEmpty(this.mappingResources)) {
229                                entityManagerFactoryBean.setMappingResources(this.mappingResources);
230                        }
231                        URL rootLocation = EntityManagerFactoryBuilder.this.persistenceUnitRootLocation;
232                        if (rootLocation != null) {
233                                entityManagerFactoryBean
234                                                .setPersistenceUnitRootLocation(rootLocation.toString());
235                        }
236                        if (EntityManagerFactoryBuilder.this.bootstrapExecutor != null) {
237                                entityManagerFactoryBean.setBootstrapExecutor(
238                                                EntityManagerFactoryBuilder.this.bootstrapExecutor);
239                        }
240                        return entityManagerFactoryBean;
241                }
242
243        }
244
245}