View Javadoc
1   /*
2    * Copyright 2002-2014 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.IOException;
20  import java.lang.annotation.Annotation;
21  import java.lang.reflect.Method;
22  import java.util.Properties;
23  import java.util.Set;
24  import java.util.TreeSet;
25  import javax.persistence.AttributeConverter;
26  import javax.persistence.Embeddable;
27  import javax.persistence.Entity;
28  import javax.persistence.MappedSuperclass;
29  import javax.sql.DataSource;
30  import javax.transaction.TransactionManager;
31  
32  import org.hibernate.HibernateException;
33  import org.hibernate.MappingException;
34  import org.hibernate.SessionFactory;
35  import org.hibernate.cache.spi.RegionFactory;
36  import org.hibernate.cfg.AvailableSettings;
37  import org.hibernate.cfg.Configuration;
38  import org.hibernate.cfg.Environment;
39  import org.hibernate.cfg.Settings;
40  import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory;
41  import org.hibernate.service.ServiceRegistry;
42  
43  import org.springframework.core.io.Resource;
44  import org.springframework.core.io.ResourceLoader;
45  import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
46  import org.springframework.core.io.support.ResourcePatternResolver;
47  import org.springframework.core.io.support.ResourcePatternUtils;
48  import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
49  import org.springframework.core.type.classreading.MetadataReader;
50  import org.springframework.core.type.classreading.MetadataReaderFactory;
51  import org.springframework.core.type.filter.AnnotationTypeFilter;
52  import org.springframework.core.type.filter.TypeFilter;
53  import org.springframework.transaction.jta.JtaTransactionManager;
54  import org.springframework.util.Assert;
55  import org.springframework.util.ClassUtils;
56  
57  /**
58   * A Spring-provided extension of the standard Hibernate {@link Configuration} class,
59   * adding {@link SpringSessionContext} as a default and providing convenient ways
60   * to specify a DataSource and an application class loader.
61   *
62   * <p>This is designed for programmatic use, e.g. in {@code @Bean} factory methods.
63   * Consider using {@link LocalSessionFactoryBean} for XML bean definition files.
64   *
65   * <p><b>Requires Hibernate 4.0 or higher.</b> As of Spring 4.0, it is compatible with
66   * (the quite refactored) Hibernate 4.3 as well. We recommend using the latest
67   * Hibernate 4.2.x or 4.3.x version, depending on whether you need to remain JPA 2.0
68   * compatible at runtime (Hibernate 4.2) or can upgrade to JPA 2.1 (Hibernate 4.3).
69   *
70   * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make
71   * sure to either use the {@link #setJtaTransactionManager} method or to set the
72   * "hibernate.transaction.factory_class" property to {@link CMTTransactionFactory}.
73   * Otherwise, Hibernate's smart flushing mechanism won't work properly.
74   *
75   * @author Juergen Hoeller
76   * @since 3.1
77   * @see LocalSessionFactoryBean
78   */
79  @SuppressWarnings("serial")
80  public class LocalSessionFactoryBuilder extends Configuration {
81  
82  	private static final String RESOURCE_PATTERN = "/**/*.class";
83  
84  	private static final String PACKAGE_INFO_SUFFIX = ".package-info";
85  
86  	private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] {
87  			new AnnotationTypeFilter(Entity.class, false),
88  			new AnnotationTypeFilter(Embeddable.class, false),
89  			new AnnotationTypeFilter(MappedSuperclass.class, false)};
90  
91  
92  	private static TypeFilter converterTypeFilter;
93  
94  	static {
95  		try {
96  			@SuppressWarnings("unchecked")
97  			Class<? extends Annotation> converterAnnotation = (Class<? extends Annotation>)
98  					ClassUtils.forName("javax.persistence.Converter", LocalSessionFactoryBuilder.class.getClassLoader());
99  			converterTypeFilter = new AnnotationTypeFilter(converterAnnotation, false);
100 		}
101 		catch (ClassNotFoundException ex) {
102 			// JPA 2.1 API not available - Hibernate <4.3
103 		}
104 	}
105 
106 
107 	private final ResourcePatternResolver resourcePatternResolver;
108 
109 	private RegionFactory cacheRegionFactory;
110 
111 	private TypeFilter[] entityTypeFilters = DEFAULT_ENTITY_TYPE_FILTERS;
112 
113 
114 	/**
115 	 * Create a new LocalSessionFactoryBuilder for the given DataSource.
116 	 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
117 	 * (may be {@code null})
118 	 */
119 	public LocalSessionFactoryBuilder(DataSource dataSource) {
120 		this(dataSource, new PathMatchingResourcePatternResolver());
121 	}
122 
123 	/**
124 	 * Create a new LocalSessionFactoryBuilder for the given DataSource.
125 	 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
126 	 * (may be {@code null})
127 	 * @param classLoader the ClassLoader to load application classes from
128 	 */
129 	public LocalSessionFactoryBuilder(DataSource dataSource, ClassLoader classLoader) {
130 		this(dataSource, new PathMatchingResourcePatternResolver(classLoader));
131 	}
132 
133 	/**
134 	 * Create a new LocalSessionFactoryBuilder for the given DataSource.
135 	 * @param dataSource the JDBC DataSource that the resulting Hibernate SessionFactory should be using
136 	 * (may be {@code null})
137 	 * @param resourceLoader the ResourceLoader to load application classes from
138 	 */
139 	@SuppressWarnings("deprecation")  // to be able to build against Hibernate 4.3
140 	public LocalSessionFactoryBuilder(DataSource dataSource, ResourceLoader resourceLoader) {
141 		getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName());
142 		if (dataSource != null) {
143 			getProperties().put(Environment.DATASOURCE, dataSource);
144 		}
145 		// APP_CLASSLOADER is deprecated as of Hibernate 4.3 but we need to remain compatible with 4.0+
146 		getProperties().put(AvailableSettings.APP_CLASSLOADER, resourceLoader.getClassLoader());
147 		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
148 	}
149 
150 
151 	/**
152 	 * Set the Spring {@link JtaTransactionManager} or the JTA {@link TransactionManager}
153 	 * to be used with Hibernate, if any. Allows for using a Spring-managed transaction
154 	 * manager for Hibernate 4's session and cache synchronization, with the
155 	 * "hibernate.transaction.jta.platform" automatically set to it. Also sets
156 	 * "hibernate.transaction.factory_class" to {@link CMTTransactionFactory},
157 	 * instructing Hibernate to interact with externally managed transactions.
158 	 * <p>A passed-in Spring {@link JtaTransactionManager} needs to contain a JTA
159 	 * {@link TransactionManager} reference to be usable here, except for the WebSphere
160 	 * case where we'll automatically set {@code WebSphereExtendedJtaPlatform} accordingly.
161 	 * <p>Note: If this is set, the Hibernate settings should not contain a JTA platform
162 	 * setting to avoid meaningless double configuration.
163 	 */
164 	public LocalSessionFactoryBuilder setJtaTransactionManager(Object jtaTransactionManager) {
165 		Assert.notNull(jtaTransactionManager, "Transaction manager reference must not be null");
166 		if (jtaTransactionManager instanceof JtaTransactionManager) {
167 			boolean webspherePresent = ClassUtils.isPresent("com.ibm.wsspi.uow.UOWManager", getClass().getClassLoader());
168 			if (webspherePresent) {
169 				getProperties().put(AvailableSettings.JTA_PLATFORM,
170 						ConfigurableJtaPlatform.getJtaPlatformBasePackage() + "internal.WebSphereExtendedJtaPlatform");
171 			}
172 			else {
173 				JtaTransactionManager jtaTm = (JtaTransactionManager) jtaTransactionManager;
174 				if (jtaTm.getTransactionManager() == null) {
175 					throw new IllegalArgumentException(
176 							"Can only apply JtaTransactionManager which has a TransactionManager reference set");
177 				}
178 				getProperties().put(AvailableSettings.JTA_PLATFORM,
179 						new ConfigurableJtaPlatform(jtaTm.getTransactionManager(), jtaTm.getUserTransaction(),
180 								jtaTm.getTransactionSynchronizationRegistry()).getJtaPlatformProxy());
181 			}
182 		}
183 		else if (jtaTransactionManager instanceof TransactionManager) {
184 			getProperties().put(AvailableSettings.JTA_PLATFORM,
185 					new ConfigurableJtaPlatform((TransactionManager) jtaTransactionManager, null, null).getJtaPlatformProxy());
186 		}
187 		else {
188 			throw new IllegalArgumentException(
189 					"Unknown transaction manager type: " + jtaTransactionManager.getClass().getName());
190 		}
191 		getProperties().put(AvailableSettings.TRANSACTION_STRATEGY, new CMTTransactionFactory());
192 		return this;
193 	}
194 
195 	/**
196 	 * Set a Hibernate 4.1/4.2/4.3 {@code MultiTenantConnectionProvider} to be passed
197 	 * on to the SessionFactory: as an instance, a Class, or a String class name.
198 	 * <p>Note that the package location of the {@code MultiTenantConnectionProvider}
199 	 * interface changed between Hibernate 4.2 and 4.3. This method accepts both variants.
200 	 * @since 4.0
201 	 * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER
202 	 */
203 	public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(Object multiTenantConnectionProvider) {
204 		getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
205 		return this;
206 	}
207 
208 	/**
209 	 * Set a Hibernate 4.1/4.2/4.3 {@code CurrentTenantIdentifierResolver} to be passed
210 	 * on to the SessionFactory: as an instance, a Class, or a String class name.
211 	 * @since 4.0
212 	 * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER
213 	 */
214 	public LocalSessionFactoryBuilder setCurrentTenantIdentifierResolver(Object currentTenantIdentifierResolver) {
215 		getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
216 		return this;
217 	}
218 
219 	/**
220 	 * Set the Hibernate RegionFactory to use for the SessionFactory.
221 	 * Allows for using a Spring-managed RegionFactory instance.
222 	 * <p>Note: If this is set, the Hibernate settings should not define a
223 	 * cache provider to avoid meaningless double configuration.
224 	 * @since 4.0
225 	 * @see org.hibernate.cache.spi.RegionFactory
226 	 */
227 	public LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegionFactory) {
228 		this.cacheRegionFactory = cacheRegionFactory;
229 		return this;
230 	}
231 
232 	/**
233 	 * Specify custom type filters for Spring-based scanning for entity classes.
234 	 * <p>Default is to search all specified packages for classes annotated with
235 	 * {@code @javax.persistence.Entity}, {@code @javax.persistence.Embeddable}
236 	 * or {@code @javax.persistence.MappedSuperclass}.
237 	 * @since 4.1
238 	 * @see #scanPackages
239 	 */
240 	public LocalSessionFactoryBuilder setEntityTypeFilters(TypeFilter... entityTypeFilters) {
241 		this.entityTypeFilters = entityTypeFilters;
242 		return this;
243 	}
244 
245 	/**
246 	 * Add the given annotated classes in a batch.
247 	 * @see #addAnnotatedClass
248 	 * @see #scanPackages
249 	 */
250 	public LocalSessionFactoryBuilder addAnnotatedClasses(Class<?>... annotatedClasses) {
251 		for (Class<?> annotatedClass : annotatedClasses) {
252 			addAnnotatedClass(annotatedClass);
253 		}
254 		return this;
255 	}
256 
257 	/**
258 	 * Add the given annotated packages in a batch.
259 	 * @see #addPackage
260 	 * @see #scanPackages
261 	 */
262 	public LocalSessionFactoryBuilder addPackages(String... annotatedPackages) {
263 		for (String annotatedPackage : annotatedPackages) {
264 			addPackage(annotatedPackage);
265 		}
266 		return this;
267 	}
268 
269 	/**
270 	 * Perform Spring-based scanning for entity classes, registering them
271 	 * as annotated classes with this {@code Configuration}.
272 	 * @param packagesToScan one or more Java package names
273 	 * @throws HibernateException if scanning fails for any reason
274 	 */
275 	public LocalSessionFactoryBuilder scanPackages(String... packagesToScan) throws HibernateException {
276 		Set<String> entityClassNames = new TreeSet<String>();
277 		Set<String> converterClassNames = new TreeSet<String>();
278 		Set<String> packageNames = new TreeSet<String>();
279 		try {
280 			for (String pkg : packagesToScan) {
281 				String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
282 						ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
283 				Resource[] resources = this.resourcePatternResolver.getResources(pattern);
284 				MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
285 				for (Resource resource : resources) {
286 					if (resource.isReadable()) {
287 						MetadataReader reader = readerFactory.getMetadataReader(resource);
288 						String className = reader.getClassMetadata().getClassName();
289 						if (matchesEntityTypeFilter(reader, readerFactory)) {
290 							entityClassNames.add(className);
291 						}
292 						else if (converterTypeFilter != null && converterTypeFilter.match(reader, readerFactory)) {
293 							converterClassNames.add(className);
294 						}
295 						else if (className.endsWith(PACKAGE_INFO_SUFFIX)) {
296 							packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
297 						}
298 					}
299 				}
300 			}
301 		}
302 		catch (IOException ex) {
303 			throw new MappingException("Failed to scan classpath for unlisted classes", ex);
304 		}
305 		try {
306 			ClassLoader cl = this.resourcePatternResolver.getClassLoader();
307 			for (String className : entityClassNames) {
308 				addAnnotatedClass(cl.loadClass(className));
309 			}
310 			for (String className : converterClassNames) {
311 				ConverterRegistrationDelegate.registerConverter(this, cl.loadClass(className));
312 			}
313 			for (String packageName : packageNames) {
314 				addPackage(packageName);
315 			}
316 		}
317 		catch (ClassNotFoundException ex) {
318 			throw new MappingException("Failed to load annotated classes from classpath", ex);
319 		}
320 		return this;
321 	}
322 
323 	/**
324 	 * Check whether any of the configured entity type filters matches
325 	 * the current class descriptor contained in the metadata reader.
326 	 */
327 	private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
328 		if (this.entityTypeFilters != null) {
329 			for (TypeFilter filter : this.entityTypeFilters) {
330 				if (filter.match(reader, readerFactory)) {
331 					return true;
332 				}
333 			}
334 		}
335 		return false;
336 	}
337 
338 
339 	// Overridden methods from Hibernate's Configuration class
340 
341 	@Override
342 	public Settings buildSettings(Properties props, ServiceRegistry serviceRegistry) throws HibernateException {
343 		Settings settings = super.buildSettings(props, serviceRegistry);
344 		if (this.cacheRegionFactory != null) {
345 			try {
346 				Method setRegionFactory = Settings.class.getDeclaredMethod("setRegionFactory", RegionFactory.class);
347 				setRegionFactory.setAccessible(true);
348 				setRegionFactory.invoke(settings, this.cacheRegionFactory);
349 			}
350 			catch (Exception ex) {
351 				throw new IllegalStateException("Failed to invoke Hibernate's setRegionFactory method", ex);
352 			}
353 		}
354 		return settings;
355 	}
356 
357 	/**
358 	 * Build the {@code SessionFactory}.
359 	 */
360 	@Override
361 	@SuppressWarnings("deprecation")
362 	public SessionFactory buildSessionFactory() throws HibernateException {
363 		ClassLoader appClassLoader = (ClassLoader) getProperties().get(AvailableSettings.APP_CLASSLOADER);
364 		Thread currentThread = Thread.currentThread();
365 		ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
366 		boolean overrideClassLoader =
367 				(appClassLoader != null && !appClassLoader.equals(threadContextClassLoader));
368 		if (overrideClassLoader) {
369 			currentThread.setContextClassLoader(appClassLoader);
370 		}
371 		try {
372 			return super.buildSessionFactory();
373 		}
374 		finally {
375 			if (overrideClassLoader) {
376 				currentThread.setContextClassLoader(threadContextClassLoader);
377 			}
378 		}
379 	}
380 
381 
382 	/**
383 	 * Inner class to avoid hard dependency on JPA 2.1 / Hibernate 4.3.
384 	 */
385 	private static class ConverterRegistrationDelegate {
386 
387 		@SuppressWarnings("unchecked")
388 		public static void registerConverter(Configuration config, Class<?> converterClass) {
389 			config.addAttributeConverter((Class<? extends AttributeConverter<?, ?>>) converterClass);
390 		}
391 	}
392 
393 }