1 /*
2  * Copyright 2002-2012 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.orm.hibernate4;
18 
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.Properties;
22 import javax.sql.DataSource;
23 
24 import org.hibernate.Interceptor;
25 import org.hibernate.SessionFactory;
26 import org.hibernate.cfg.Configuration;
27 import org.hibernate.cfg.NamingStrategy;
28 
29 import org.springframework.beans.factory.DisposableBean;
30 import org.springframework.beans.factory.FactoryBean;
31 import org.springframework.beans.factory.InitializingBean;
32 import org.springframework.context.ResourceLoaderAware;
33 import org.springframework.core.io.ClassPathResource;
34 import org.springframework.core.io.Resource;
35 import org.springframework.core.io.ResourceLoader;
36 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
37 import org.springframework.core.io.support.ResourcePatternResolver;
38 import org.springframework.core.io.support.ResourcePatternUtils;
39 
40 /**
41  * {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate
42  * {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared
43  * Hibernate SessionFactory in a Spring application context; the SessionFactory can
44  * then be passed to Hibernate-based data access objects via dependency injection.
45  *
46  * <p><b>NOTE:</b> This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher.
47  * It is similar in role to the same-named class in the <code>orm.hibernate3</code> package.
48  * However, in practice, it is closer to <code>AnnotationSessionFactoryBean</code> since
49  * its core purpose is to bootstrap a <code>SessionFactory</code> from annotation scanning.
50  *
51  * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make
52  * sure to either specify the {@link #setJtaTransactionManager "jtaTransactionManager"}
53  * bean property or to set the "hibernate.transaction.factory_class" property to
54  * {@link org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory}.
55  * Otherwise, Hibernate's smart flushing mechanism won't work properly.
56  *
57  * @author Juergen Hoeller
58  * @since 3.1
59  * @see #setDataSource
60  * @see #setPackagesToScan
61  */
62 public class LocalSessionFactoryBean extends HibernateExceptionTranslator
63 		implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {
64 
65 	private DataSource dataSource;
66 
67 	private Resource[] configLocations;
68 
69 	private String[] mappingResources;
70 
71 	private Resource[] mappingLocations;
72 
73 	private Resource[] cacheableMappingLocations;
74 
75 	private Resource[] mappingJarLocations;
76 
77 	private Resource[] mappingDirectoryLocations;
78 
79 	private Interceptor entityInterceptor;
80 
81 	private NamingStrategy namingStrategy;
82 
83 	private Properties hibernateProperties;
84 
85 	private Class<?>[] annotatedClasses;
86 
87 	private String[] annotatedPackages;
88 
89 	private String[] packagesToScan;
90 
91 	private Object jtaTransactionManager;
92 
93 	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
94 
95 	private Configuration configuration;
96 
97 	private SessionFactory sessionFactory;
98 
99 
100 	/**
101 	 * Set the DataSource to be used by the SessionFactory.
102 	 * If set, this will override corresponding settings in Hibernate properties.
103 	 * <p>If this is set, the Hibernate settings should not define
104 	 * a connection provider to avoid meaningless double configuration.
105 	 */
setDataSource(DataSource dataSource)106 	public void setDataSource(DataSource dataSource) {
107 		this.dataSource = dataSource;
108 	}
109 
110 	/**
111 	 * Set the location of a single Hibernate XML config file, for example as
112 	 * classpath resource "classpath:hibernate.cfg.xml".
113 	 * <p>Note: Can be omitted when all necessary properties and mapping
114 	 * resources are specified locally via this bean.
115 	 * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
116 	 */
setConfigLocation(Resource configLocation)117 	public void setConfigLocation(Resource configLocation) {
118 		this.configLocations = new Resource[] {configLocation};
119 	}
120 
121 	/**
122 	 * Set the locations of multiple Hibernate XML config files, for example as
123 	 * classpath resources "classpath:hibernate.cfg.xml,classpath:extension.cfg.xml".
124 	 * <p>Note: Can be omitted when all necessary properties and mapping
125 	 * resources are specified locally via this bean.
126 	 * @see org.hibernate.cfg.Configuration#configure(java.net.URL)
127 	 */
setConfigLocations(Resource[] configLocations)128 	public void setConfigLocations(Resource[] configLocations) {
129 		this.configLocations = configLocations;
130 	}
131 
132 	/**
133 	 * Set Hibernate mapping resources to be found in the class path,
134 	 * like "example.hbm.xml" or "mypackage/example.hbm.xml".
135 	 * Analogous to mapping entries in a Hibernate XML config file.
136 	 * Alternative to the more generic setMappingLocations method.
137 	 * <p>Can be used to add to mappings from a Hibernate XML config file,
138 	 * or to specify all mappings locally.
139 	 * @see #setMappingLocations
140 	 * @see org.hibernate.cfg.Configuration#addResource
141 	 */
setMappingResources(String[] mappingResources)142 	public void setMappingResources(String[] mappingResources) {
143 		this.mappingResources = mappingResources;
144 	}
145 
146 	/**
147 	 * Set locations of Hibernate mapping files, for example as classpath
148 	 * resource "classpath:example.hbm.xml". Supports any resource location
149 	 * via Spring's resource abstraction, for example relative paths like
150 	 * "WEB-INF/mappings/example.hbm.xml" when running in an application context.
151 	 * <p>Can be used to add to mappings from a Hibernate XML config file,
152 	 * or to specify all mappings locally.
153 	 * @see org.hibernate.cfg.Configuration#addInputStream
154 	 */
setMappingLocations(Resource[] mappingLocations)155 	public void setMappingLocations(Resource[] mappingLocations) {
156 		this.mappingLocations = mappingLocations;
157 	}
158 
159 	/**
160 	 * Set locations of cacheable Hibernate mapping files, for example as web app
161 	 * resource "/WEB-INF/mapping/example.hbm.xml". Supports any resource location
162 	 * via Spring's resource abstraction, as long as the resource can be resolved
163 	 * in the file system.
164 	 * <p>Can be used to add to mappings from a Hibernate XML config file,
165 	 * or to specify all mappings locally.
166 	 * @see org.hibernate.cfg.Configuration#addCacheableFile(java.io.File)
167 	 */
setCacheableMappingLocations(Resource[] cacheableMappingLocations)168 	public void setCacheableMappingLocations(Resource[] cacheableMappingLocations) {
169 		this.cacheableMappingLocations = cacheableMappingLocations;
170 	}
171 
172 	/**
173 	 * Set locations of jar files that contain Hibernate mapping resources,
174 	 * like "WEB-INF/lib/example.hbm.jar".
175 	 * <p>Can be used to add to mappings from a Hibernate XML config file,
176 	 * or to specify all mappings locally.
177 	 * @see org.hibernate.cfg.Configuration#addJar(java.io.File)
178 	 */
setMappingJarLocations(Resource[] mappingJarLocations)179 	public void setMappingJarLocations(Resource[] mappingJarLocations) {
180 		this.mappingJarLocations = mappingJarLocations;
181 	}
182 
183 	/**
184 	 * Set locations of directories that contain Hibernate mapping resources,
185 	 * like "WEB-INF/mappings".
186 	 * <p>Can be used to add to mappings from a Hibernate XML config file,
187 	 * or to specify all mappings locally.
188 	 * @see org.hibernate.cfg.Configuration#addDirectory(java.io.File)
189 	 */
setMappingDirectoryLocations(Resource[] mappingDirectoryLocations)190 	public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations) {
191 		this.mappingDirectoryLocations = mappingDirectoryLocations;
192 	}
193 
194 	/**
195 	 * Set a Hibernate entity interceptor that allows to inspect and change
196 	 * property values before writing to and reading from the database.
197 	 * Will get applied to any new Session created by this factory.
198 	 * @see org.hibernate.cfg.Configuration#setInterceptor
199 	 */
setEntityInterceptor(Interceptor entityInterceptor)200 	public void setEntityInterceptor(Interceptor entityInterceptor) {
201 		this.entityInterceptor = entityInterceptor;
202 	}
203 
204 	/**
205 	 * Set a Hibernate NamingStrategy for the SessionFactory, determining the
206 	 * physical column and table names given the info in the mapping document.
207 	 * @see org.hibernate.cfg.Configuration#setNamingStrategy
208 	 */
setNamingStrategy(NamingStrategy namingStrategy)209 	public void setNamingStrategy(NamingStrategy namingStrategy) {
210 		this.namingStrategy = namingStrategy;
211 	}
212 
213 	/**
214 	 * Set Hibernate properties, such as "hibernate.dialect".
215 	 * <p>Note: Do not specify a transaction provider here when using
216 	 * Spring-driven transactions. It is also advisable to omit connection
217 	 * provider settings and use a Spring-set DataSource instead.
218 	 * @see #setDataSource
219 	 */
setHibernateProperties(Properties hibernateProperties)220 	public void setHibernateProperties(Properties hibernateProperties) {
221 		this.hibernateProperties = hibernateProperties;
222 	}
223 
224 	/**
225 	 * Return the Hibernate properties, if any. Mainly available for
226 	 * configuration through property paths that specify individual keys.
227 	 */
getHibernateProperties()228 	public Properties getHibernateProperties() {
229 		if (this.hibernateProperties == null) {
230 			this.hibernateProperties = new Properties();
231 		}
232 		return this.hibernateProperties;
233 	}
234 
235 	/**
236 	 * Specify annotated entity classes to register with this Hibernate SessionFactory.
237 	 * @see org.hibernate.cfg.Configuration#addAnnotatedClass(Class)
238 	 */
setAnnotatedClasses(Class<?>[] annotatedClasses)239 	public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
240 		this.annotatedClasses = annotatedClasses;
241 	}
242 
243 	/**
244 	 * Specify the names of annotated packages, for which package-level
245 	 * annotation metadata will be read.
246 	 * @see org.hibernate.cfg.Configuration#addPackage(String)
247 	 */
setAnnotatedPackages(String[] annotatedPackages)248 	public void setAnnotatedPackages(String[] annotatedPackages) {
249 		this.annotatedPackages = annotatedPackages;
250 	}
251 
252 	/**
253 	 * Specify packages to search for autodetection of your entity classes in the
254 	 * classpath. This is analogous to Spring's component-scan feature
255 	 * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}).
256 	 */
setPackagesToScan(String... packagesToScan)257 	public void setPackagesToScan(String... packagesToScan) {
258 		this.packagesToScan = packagesToScan;
259 	}
260 
261 	/**
262 	 * Set the Spring {@link org.springframework.transaction.jta.JtaTransactionManager}
263 	 * or the JTA {@link javax.transaction.TransactionManager} to be used with Hibernate,
264 	 * if any.
265 	 * @see LocalSessionFactoryBuilder#setJtaTransactionManager
266 	 */
setJtaTransactionManager(Object jtaTransactionManager)267 	public void setJtaTransactionManager(Object jtaTransactionManager) {
268 		this.jtaTransactionManager = jtaTransactionManager;
269 	}
270 
setResourceLoader(ResourceLoader resourceLoader)271 	public void setResourceLoader(ResourceLoader resourceLoader) {
272 		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
273 	}
274 
275 
afterPropertiesSet()276 	public void afterPropertiesSet() throws IOException {
277 		LocalSessionFactoryBuilder sfb = new LocalSessionFactoryBuilder(this.dataSource, this.resourcePatternResolver);
278 
279 		if (this.configLocations != null) {
280 			for (Resource resource : this.configLocations) {
281 				// Load Hibernate configuration from given location.
282 				sfb.configure(resource.getURL());
283 			}
284 		}
285 
286 		if (this.mappingResources != null) {
287 			// Register given Hibernate mapping definitions, contained in resource files.
288 			for (String mapping : this.mappingResources) {
289 				Resource mr = new ClassPathResource(mapping.trim(), this.resourcePatternResolver.getClassLoader());
290 				sfb.addInputStream(mr.getInputStream());
291 			}
292 		}
293 
294 		if (this.mappingLocations != null) {
295 			// Register given Hibernate mapping definitions, contained in resource files.
296 			for (Resource resource : this.mappingLocations) {
297 				sfb.addInputStream(resource.getInputStream());
298 			}
299 		}
300 
301 		if (this.cacheableMappingLocations != null) {
302 			// Register given cacheable Hibernate mapping definitions, read from the file system.
303 			for (Resource resource : this.cacheableMappingLocations) {
304 				sfb.addCacheableFile(resource.getFile());
305 			}
306 		}
307 
308 		if (this.mappingJarLocations != null) {
309 			// Register given Hibernate mapping definitions, contained in jar files.
310 			for (Resource resource : this.mappingJarLocations) {
311 				sfb.addJar(resource.getFile());
312 			}
313 		}
314 
315 		if (this.mappingDirectoryLocations != null) {
316 			// Register all Hibernate mapping definitions in the given directories.
317 			for (Resource resource : this.mappingDirectoryLocations) {
318 				File file = resource.getFile();
319 				if (!file.isDirectory()) {
320 					throw new IllegalArgumentException(
321 							"Mapping directory location [" + resource + "] does not denote a directory");
322 				}
323 				sfb.addDirectory(file);
324 			}
325 		}
326 
327 		if (this.entityInterceptor != null) {
328 			sfb.setInterceptor(this.entityInterceptor);
329 		}
330 
331 		if (this.namingStrategy != null) {
332 			sfb.setNamingStrategy(this.namingStrategy);
333 		}
334 
335 		if (this.hibernateProperties != null) {
336 			sfb.addProperties(this.hibernateProperties);
337 		}
338 
339 		if (this.annotatedClasses != null) {
340 			sfb.addAnnotatedClasses(this.annotatedClasses);
341 		}
342 
343 		if (this.annotatedPackages != null) {
344 			sfb.addPackages(this.annotatedPackages);
345 		}
346 
347 		if (this.packagesToScan != null) {
348 			sfb.scanPackages(this.packagesToScan);
349 		}
350 
351 		if (this.jtaTransactionManager != null) {
352 			sfb.setJtaTransactionManager(this.jtaTransactionManager);
353 		}
354 
355 		// Build SessionFactory instance.
356 		this.configuration = sfb;
357 		this.sessionFactory = buildSessionFactory(sfb);
358 	}
359 
360 	/**
361 	 * Subclasses can override this method to perform custom initialization
362 	 * of the SessionFactory instance, creating it via the given Configuration
363 	 * object that got prepared by this LocalSessionFactoryBean.
364 	 * <p>The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory.
365 	 * A custom implementation could prepare the instance in a specific way (e.g. applying
366 	 * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass.
367 	 * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean
368 	 * @return the SessionFactory instance
369 	 * @see LocalSessionFactoryBuilder#buildSessionFactory
370 	 */
buildSessionFactory(LocalSessionFactoryBuilder sfb)371 	protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
372 		return sfb.buildSessionFactory();
373 	}
374 
375 	/**
376 	 * Return the Hibernate Configuration object used to build the SessionFactory.
377 	 * Allows for access to configuration metadata stored there (rarely needed).
378 	 * @throws IllegalStateException if the Configuration object has not been initialized yet
379 	 */
getConfiguration()380 	public final Configuration getConfiguration() {
381 		if (this.configuration == null) {
382 			throw new IllegalStateException("Configuration not initialized yet");
383 		}
384 		return this.configuration;
385 	}
386 
387 
getObject()388 	public SessionFactory getObject() {
389 		return this.sessionFactory;
390 	}
391 
getObjectType()392 	public Class<?> getObjectType() {
393 		return (this.sessionFactory != null ? this.sessionFactory.getClass() : SessionFactory.class);
394 	}
395 
isSingleton()396 	public boolean isSingleton() {
397 		return true;
398 	}
399 
400 
destroy()401 	public void destroy() {
402 		this.sessionFactory.close();
403 	}
404 
405 }
406