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.IOException; 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Method; 022import java.util.Properties; 023import java.util.Set; 024import java.util.TreeSet; 025import javax.persistence.AttributeConverter; 026import javax.persistence.Embeddable; 027import javax.persistence.Entity; 028import javax.persistence.MappedSuperclass; 029import javax.sql.DataSource; 030import javax.transaction.TransactionManager; 031 032import org.hibernate.HibernateException; 033import org.hibernate.MappingException; 034import org.hibernate.SessionFactory; 035import org.hibernate.cache.spi.RegionFactory; 036import org.hibernate.cfg.AvailableSettings; 037import org.hibernate.cfg.Configuration; 038import org.hibernate.cfg.Environment; 039import org.hibernate.cfg.Settings; 040import org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory; 041import org.hibernate.service.ServiceRegistry; 042 043import org.springframework.core.io.Resource; 044import org.springframework.core.io.ResourceLoader; 045import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 046import org.springframework.core.io.support.ResourcePatternResolver; 047import org.springframework.core.io.support.ResourcePatternUtils; 048import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 049import org.springframework.core.type.classreading.MetadataReader; 050import org.springframework.core.type.classreading.MetadataReaderFactory; 051import org.springframework.core.type.filter.AnnotationTypeFilter; 052import org.springframework.core.type.filter.TypeFilter; 053import org.springframework.transaction.jta.JtaTransactionManager; 054import org.springframework.util.Assert; 055import org.springframework.util.ClassUtils; 056 057/** 058 * A Spring-provided extension of the standard Hibernate {@link Configuration} class, 059 * adding {@link SpringSessionContext} as a default and providing convenient ways 060 * to specify a DataSource and an application class loader. 061 * 062 * <p>This is designed for programmatic use, e.g. in {@code @Bean} factory methods. 063 * Consider using {@link LocalSessionFactoryBean} for XML bean definition files. 064 * 065 * <p><b>Requires Hibernate 4.0 or higher.</b> As of Spring 4.0, it is compatible with 066 * (the quite refactored) Hibernate 4.3 as well. We recommend using the latest 067 * Hibernate 4.2.x or 4.3.x version, depending on whether you need to remain JPA 2.0 068 * compatible at runtime (Hibernate 4.2) or can upgrade to JPA 2.1 (Hibernate 4.3). 069 * 070 * <p><b>NOTE:</b> To set up Hibernate 4 for Spring-driven JTA transactions, make 071 * sure to either use the {@link #setJtaTransactionManager} method or to set the 072 * "hibernate.transaction.factory_class" property to {@link CMTTransactionFactory}. 073 * Otherwise, Hibernate's smart flushing mechanism won't work properly. 074 * 075 * @author Juergen Hoeller 076 * @since 3.1 077 * @see LocalSessionFactoryBean 078 */ 079@SuppressWarnings("serial") 080public class LocalSessionFactoryBuilder extends Configuration { 081 082 private static final String RESOURCE_PATTERN = "/**/*.class"; 083 084 private static final String PACKAGE_INFO_SUFFIX = ".package-info"; 085 086 private static final TypeFilter[] DEFAULT_ENTITY_TYPE_FILTERS = new TypeFilter[] { 087 new AnnotationTypeFilter(Entity.class, false), 088 new AnnotationTypeFilter(Embeddable.class, false), 089 new AnnotationTypeFilter(MappedSuperclass.class, false)}; 090 091 092 private static TypeFilter converterTypeFilter; 093 094 static { 095 try { 096 @SuppressWarnings("unchecked") 097 Class<? extends Annotation> converterAnnotation = (Class<? extends Annotation>) 098 ClassUtils.forName("javax.persistence.Converter", LocalSessionFactoryBuilder.class.getClassLoader()); 099 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}