001/*
002 * Copyright 2002-2014 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 *      https://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.orm.hibernate4;
018
019import java.io.File;
020import java.io.IOException;
021import java.util.Properties;
022import javax.sql.DataSource;
023
024import org.hibernate.Interceptor;
025import org.hibernate.SessionFactory;
026import org.hibernate.cache.spi.RegionFactory;
027import org.hibernate.cfg.Configuration;
028
029import org.springframework.beans.factory.DisposableBean;
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.InitializingBean;
032import org.springframework.context.ResourceLoaderAware;
033import org.springframework.core.io.ClassPathResource;
034import org.springframework.core.io.Resource;
035import org.springframework.core.io.ResourceLoader;
036import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
037import org.springframework.core.io.support.ResourcePatternResolver;
038import org.springframework.core.io.support.ResourcePatternUtils;
039import org.springframework.core.type.filter.TypeFilter;
040
041/**
042 * {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate
043 * {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared
044 * Hibernate SessionFactory in a Spring application context; the SessionFactory can
045 * then be passed to Hibernate-based data access objects via dependency injection.
046 *
047 * <p><b>This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher.</b>
048 * As of Spring 4.0, it is compatible with (the quite refactored) Hibernate 4.3 as well.
049 * We recommend using the latest Hibernate 4.2.x or 4.3.x version, depending on whether
050 * you need to remain JPA 2.0 compatible at runtime (Hibernate 4.2) or can upgrade to
051 * JPA 2.1 (Hibernate 4.3).
052 *
053 * <p>This class is similar in role to the same-named class in the {@code orm.hibernate3}
054 * package. However, in practice, it is closer to {@code AnnotationSessionFactoryBean}
055 * since its core purpose is to bootstrap a {@code SessionFactory} from package scanning.
056 *
057 * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make
058 * sure to either specify the {@link #setJtaTransactionManager "jtaTransactionManager"}
059 * bean property or to set the "hibernate.transaction.factory_class" property to
060 * {@link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}.
061 * Otherwise, Hibernate's smart flushing mechanism won't work properly.
062 *
063 * @author Juergen Hoeller
064 * @since 3.1
065 * @see #setDataSource
066 * @see #setPackagesToScan
067 * @see LocalSessionFactoryBuilder
068 */
069public class LocalSessionFactoryBean extends HibernateExceptionTranslator
070                implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
071
072        private DataSource dataSource;
073
074        private Resource[] configLocations;
075
076        private String[] mappingResources;
077
078        private Resource[] mappingLocations;
079
080        private Resource[] cacheableMappingLocations;
081
082        private Resource[] mappingJarLocations;
083
084        private Resource[] mappingDirectoryLocations;
085
086        private Interceptor entityInterceptor;
087
088        @SuppressWarnings("deprecation")
089        private org.hibernate.cfg.NamingStrategy namingStrategy;
090
091        private Object jtaTransactionManager;
092
093        private Object multiTenantConnectionProvider;
094
095        private Object currentTenantIdentifierResolver;
096
097        private RegionFactory cacheRegionFactory;
098
099        private TypeFilter[] entityTypeFilters;
100
101        private Properties hibernateProperties;
102
103        private Class<?>[] annotatedClasses;
104
105        private String[] annotatedPackages;
106
107        private String[] packagesToScan;
108
109        private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
110
111        private Configuration configuration;
112
113        private SessionFactory sessionFactory;
114
115
116        /**
117         * Set the DataSource to be used by the SessionFactory.
118         * If set, this will override corresponding settings in Hibernate properties.
119         * <p>If this is set, the Hibernate settings should not define
120         * a connection provider to avoid meaningless double configuration.
121         */
122        public void setDataSource(DataSource dataSource) {
123                this.dataSource = dataSource;
124        }
125
126        /**
127         * Set the location of a single Hibernate XML config file, for example as
128         * classpath resource "classpath:hibernate.cfg.xml".
129         * <p>Note: Can be omitted when all necessary properties and mapping
130         * resources are specified locally via this bean.
131         * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
132         */
133        public void setConfigLocation(Resource configLocation) {
134                this.configLocations = new Resource[] {configLocation};
135        }
136
137        /**
138         * Set the locations of multiple Hibernate XML config files, for example as
139         * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
140         * <p>Note: Can be omitted when all necessary properties and mapping
141         * resources are specified locally via this bean.
142         * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
143         */
144        public void setConfigLocations(Resource... configLocations) {
145                this.configLocations = configLocations;
146        }
147
148        /**
149         * Set Hibernate mapping resources to be found in the class path,
150         * like "example.hbm.xml" or "mypackage/example.hbm.xml".
151         * Analogous to mapping entries in a Hibernate XML config file.
152         * Alternative to the more generic setMappingLocations method.
153         * <p>Can be used to add to mappings from a Hibernate XML config file,
154         * or to specify all mappings locally.
155         * @see #setMappingLocations
156         * @see org.hibernate.cfg.Configuration#addResource
157         */
158        public void setMappingResources(String... mappingResources) {
159                this.mappingResources = mappingResources;
160        }
161
162        /**
163         * Set locations of Hibernate mapping files, for example as classpath
164         * resource "classpath:example.hbm.xml". Supports any resource location
165         * via Spring's resource abstraction, for example relative paths like
166         * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
167         * <p>Can be used to add to mappings from a Hibernate XML config file,
168         * or to specify all mappings locally.
169         * @see org.hibernate.cfg.Configuration#addInputStream
170         */
171        public void setMappingLocations(Resource... mappingLocations) {
172                this.mappingLocations = mappingLocations;
173        }
174
175        /**
176         * Set locations of cacheable Hibernate mapping files, for example as web app
177         * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
178         * via Spring's resource abstraction, as long as the resource can be resolved
179         * in the file system.
180         * <p>Can be used to add to mappings from a Hibernate XML config file,
181         * or to specify all mappings locally.
182         * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File)
183         */
184        public void setCacheableMappingLocations(Resource... cacheableMappingLocations) {
185                this.cacheableMappingLocations = cacheableMappingLocations;
186        }
187
188        /**
189         * Set locations of jar files that contain Hibernate mapping resources,
190         * like "WEB-INF/lib/example.hbm.jar".
191         * <p>Can be used to add to mappings from a Hibernate XML config file,
192         * or to specify all mappings locally.
193         * @see org.hibernate.cfg.Configuration#addJar(java.io.File)
194         */
195        public void setMappingJarLocations(Resource... mappingJarLocations) {
196                this.mappingJarLocations = mappingJarLocations;
197        }
198
199        /**
200         * Set locations of directories that contain Hibernate mapping resources,
201         * like "WEB-INF/mappings".
202         * <p>Can be used to add to mappings from a Hibernate XML config file,
203         * or to specify all mappings locally.
204         * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File)
205         */
206        public void setMappingDirectoryLocations(Resource... mappingDirectoryLocations) {
207                this.mappingDirectoryLocations = mappingDirectoryLocations;
208        }
209
210        /**
211         * Set a Hibernate entity interceptor that allows to inspect and change
212         * property values before writing to and reading from the database.
213         * Will get applied to any new Session created by this factory.
214         * @see org.hibernate.cfg.Configuration#setInterceptor
215         */
216        public void setEntityInterceptor(Interceptor entityInterceptor) {
217                this.entityInterceptor = entityInterceptor;
218        }
219
220        /**
221         * Set a Hibernate NamingStrategy for the SessionFactory, determining the
222         * physical column and table names given the info in the mapping document.
223         * @see org.hibernate.cfg.Configuration#setNamingStrategy
224         */
225        @SuppressWarnings("deprecation")
226        public void setNamingStrategy(org.hibernate.cfg.NamingStrategy namingStrategy) {
227                this.namingStrategy = namingStrategy;
228        }
229
230        /**
231         * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager}
232         * or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate,
233         * if any. Implicitly sets up {@code JtaPlatform} and {@code CMTTransactionStrategy}.
234         * @see LocalSessionFactoryBuilder#setJtaTransactionManager
235         */
236        public void setJtaTransactionManager(Object jtaTransactionManager) {
237                this.jtaTransactionManager = jtaTransactionManager;
238        }
239
240        /**
241         * Set a Hibernate 4.1/4.2/4.3 {@code MultiTenantConnectionProvider} to be passed
242         * on to the SessionFactory: as an instance, a Class, or a String class name.
243         * <p>Note that the package location of the {@code MultiTenantConnectionProvider}
244         * interface changed between Hibernate 4.2 and 4.3. This method accepts both variants.
245         * @since 4.0
246         * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider
247         */
248        public void setMultiTenantConnectionProvider(Object multiTenantConnectionProvider) {
249                this.multiTenantConnectionProvider = multiTenantConnectionProvider;
250        }
251
252        /**
253         * Set a Hibernate 4.1/4.2/4.3 {@code CurrentTenantIdentifierResolver} to be passed
254         * on to the SessionFactory: as an instance, a Class, or a String class name.
255         * @since 4.0
256         * @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver
257         */
258        public void setCurrentTenantIdentifierResolver(Object currentTenantIdentifierResolver) {
259                this.currentTenantIdentifierResolver = currentTenantIdentifierResolver;
260        }
261
262        /**
263         * Set the Hibernate RegionFactory to use for the SessionFactory.
264         * Allows for using a Spring-managed RegionFactory instance.
265         * <p>Note: If this is set, the Hibernate settings should not define a
266         * cache provider to avoid meaningless double configuration.
267         * @since 4.0
268         * @see org.hibernate.cache.spi.RegionFactory
269         * @see LocalSessionFactoryBuilder#setCacheRegionFactory
270         */
271        public void setCacheRegionFactory(RegionFactory cacheRegionFactory) {
272                this.cacheRegionFactory = cacheRegionFactory;
273        }
274
275        /**
276         * Specify custom type filters for Spring-based scanning for entity classes.
277         * <p>Default is to search all specified packages for classes annotated with
278         * {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
279         * or {@code @javax.persistence.MappedSuperclass}.
280         * @since 4.1
281         * @see #setPackagesToScan
282         */
283        public void setEntityTypeFilters(TypeFilter... entityTypeFilters) {
284                this.entityTypeFilters = entityTypeFilters;
285        }
286
287        /**
288         * Set Hibernate properties, such as "hibernate.dialect".
289         * <p>Note: Do not specify a transaction provider here when using
290         * Spring-driven transactions. It is also advisable to omit connection
291         * provider settings and use a Spring-set DataSource instead.
292         * @see #setDataSource
293         */
294        public void setHibernateProperties(Properties hibernateProperties) {
295                this.hibernateProperties = hibernateProperties;
296        }
297
298        /**
299         * Return the Hibernate properties, if any. Mainly available for
300         * configuration through property paths that specify individual keys.
301         */
302        public Properties getHibernateProperties() {
303                if (this.hibernateProperties == null) {
304                        this.hibernateProperties = new Properties();
305                }
306                return this.hibernateProperties;
307        }
308
309        /**
310         * Specify annotated entity classes to register with this Hibernate SessionFactory.
311         * @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class)
312         */
313        public void setAnnotatedClasses(Class<?>... annotatedClasses) {
314                this.annotatedClasses = annotatedClasses;
315        }
316
317        /**
318         * Specify the names of annotated packages, for which package-level
319         * annotation metadata will be read.
320         * @see org.hibernate.cfg.Configuration#addPackage(String)
321         */
322        public void setAnnotatedPackages(String... annotatedPackages) {
323                this.annotatedPackages = annotatedPackages;
324        }
325
326        /**
327         * Specify packages to search for autodetection of your entity classes in the
328         * classpath. This is analogous to Spring's component-scan feature
329         * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
330         */
331        public void setPackagesToScan(String... packagesToScan) {
332                this.packagesToScan = packagesToScan;
333        }
334
335        @Override
336        public void setResourceLoader(ResourceLoader resourceLoader) {
337                this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
338        }
339
340
341        @Override
342        public void afterPropertiesSet() throws IOException {
343                LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver);
344
345                if (this.configLocations != null) {
346                        for (Resource resource : this.configLocations) {
347                                // Load Hibernate configuration from given location.
348                                sfb.configure(resource.getURL());
349                        }
350                }
351
352                if (this.mappingResources != null) {
353                        // Register given Hibernate mapping definitions, contained in resource files.
354                        for (String mapping : this.mappingResources) {
355                                Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
356                                sfb.addInputStream(mr.getInputStream());
357                        }
358                }
359
360                if (this.mappingLocations != null) {
361                        // Register given Hibernate mapping definitions, contained in resource files.
362                        for (Resource resource : this.mappingLocations) {
363                                sfb.addInputStream(resource.getInputStream());
364                        }
365                }
366
367                if (this.cacheableMappingLocations != null) {
368                        // Register given cacheable Hibernate mapping definitions, read from the file system.
369                        for (Resource resource : this.cacheableMappingLocations) {
370                                sfb.addCacheableFile(resource.getFile());
371                        }
372                }
373
374                if (this.mappingJarLocations != null) {
375                        // Register given Hibernate mapping definitions, contained in jar files.
376                        for (Resource resource : this.mappingJarLocations) {
377                                sfb.addJar(resource.getFile());
378                        }
379                }
380
381                if (this.mappingDirectoryLocations != null) {
382                        // Register all Hibernate mapping definitions in the given directories.
383                        for (Resource resource : this.mappingDirectoryLocations) {
384                                File file = resource.getFile();
385                                if (!file.isDirectory()) {
386                                        throw new IllegalArgumentException(
387                                                        "Mapping directory location [" + resource + "] does not denote a directory");
388                                }
389                                sfb.addDirectory(file);
390                        }
391                }
392
393                if (this.entityInterceptor != null) {
394                        sfb.setInterceptor(this.entityInterceptor);
395                }
396
397                if (this.namingStrategy != null) {
398                        sfb.setNamingStrategy(this.namingStrategy);
399                }
400
401                if (this.jtaTransactionManager != null) {
402                        sfb.setJtaTransactionManager(this.jtaTransactionManager);
403                }
404
405                if (this.multiTenantConnectionProvider != null) {
406                        sfb.setMultiTenantConnectionProvider(this.multiTenantConnectionProvider);
407                }
408
409                if (this.currentTenantIdentifierResolver != null) {
410                        sfb.setCurrentTenantIdentifierResolver(this.currentTenantIdentifierResolver);
411                }
412
413                if (this.cacheRegionFactory != null) {
414                        sfb.setCacheRegionFactory(this.cacheRegionFactory);
415                }
416
417                if (this.entityTypeFilters != null) {
418                        sfb.setEntityTypeFilters(this.entityTypeFilters);
419                }
420
421                if (this.hibernateProperties != null) {
422                        sfb.addProperties(this.hibernateProperties);
423                }
424
425                if (this.annotatedClasses != null) {
426                        sfb.addAnnotatedClasses(this.annotatedClasses);
427                }
428
429                if (this.annotatedPackages != null) {
430                        sfb.addPackages(this.annotatedPackages);
431                }
432
433                if (this.packagesToScan != null) {
434                        sfb.scanPackages(this.packagesToScan);
435                }
436
437                // Build SessionFactory instance.
438                this.configuration = sfb;
439                this.sessionFactory = buildSessionFactory(sfb);
440        }
441
442        /**
443         * Subclasses can override this method to perform custom initialization
444         * of the SessionFactory instance, creating it via the given Configuration
445         * object that got prepared by this LocalSessionFactoryBean.
446         * <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
447         * A custom implementation could prepare the instance in a specific way (e.g. applying
448         * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
449         * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
450         * @return the SessionFactory instance
451         * @see LocalSessionFactoryBuilder#buildSessionFactory
452         */
453        protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
454                return sfb.buildSessionFactory();
455        }
456
457        /**
458         * Return the Hibernate Configuration object used to build the SessionFactory.
459         * Allows for access to configuration metadata stored there (rarely needed).
460         * @throws IllegalStateException if the Configuration object has not been initialized yet
461         */
462        public final Configuration getConfiguration() {
463                if (this.configuration == null) {
464                        throw new IllegalStateException("Configuration not initialized yet");
465                }
466                return this.configuration;
467        }
468
469
470        @Override
471        public SessionFactory getObject() {
472                return this.sessionFactory;
473        }
474
475        @Override
476        public Class<?> getObjectType() {
477                return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
478        }
479
480        @Override
481        public boolean isSingleton() {
482                return true;
483        }
484
485
486        @Override
487        public void destroy() {
488                this.sessionFactory.close();
489        }
490
491}