001/* 002 * Copyright 2002-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 * 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.jpa.persistenceunit; 018 019import java.io.IOException; 020import java.lang.annotation.Annotation; 021import java.net.URL; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.LinkedHashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import javax.persistence.Embeddable; 030import javax.persistence.Entity; 031import javax.persistence.MappedSuperclass; 032import javax.persistence.PersistenceException; 033import javax.persistence.SharedCacheMode; 034import javax.persistence.ValidationMode; 035import javax.persistence.spi.PersistenceUnitInfo; 036import javax.sql.DataSource; 037 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040 041import org.springframework.beans.factory.InitializingBean; 042import org.springframework.context.ResourceLoaderAware; 043import org.springframework.context.weaving.LoadTimeWeaverAware; 044import org.springframework.core.io.Resource; 045import org.springframework.core.io.ResourceLoader; 046import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 047import org.springframework.core.io.support.ResourcePatternResolver; 048import org.springframework.core.io.support.ResourcePatternUtils; 049import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 050import org.springframework.core.type.classreading.MetadataReader; 051import org.springframework.core.type.classreading.MetadataReaderFactory; 052import org.springframework.core.type.filter.AnnotationTypeFilter; 053import org.springframework.core.type.filter.TypeFilter; 054import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; 055import org.springframework.instrument.classloading.LoadTimeWeaver; 056import org.springframework.jdbc.datasource.lookup.DataSourceLookup; 057import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; 058import org.springframework.jdbc.datasource.lookup.MapDataSourceLookup; 059import org.springframework.util.ClassUtils; 060import org.springframework.util.ObjectUtils; 061import org.springframework.util.ResourceUtils; 062 063/** 064 * Default implementation of the {@link PersistenceUnitManager} interface. 065 * Used as internal default by 066 * {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean}. 067 * 068 * <p>Supports standard JPA scanning for {@code persistence.xml} files, 069 * with configurable file locations, JDBC DataSource lookup and load-time weaving. 070 * 071 * <p>The default XML file location is {@code classpath*:META-INF/persistence.xml}, 072 * scanning for all matching files in the classpath (as defined in the JPA specification). 073 * DataSource names are by default interpreted as JNDI names, and no load time weaving 074 * is available (which requires weaving to be turned off in the persistence provider). 075 * 076 * <p><b>NOTE: Spring's JPA support requires JPA 2.0 or higher, as of Spring 4.0.</b> 077 * Spring's persistence unit bootstrapping automatically detects JPA 2.1 at runtime. 078 * 079 * @author Juergen Hoeller 080 * @since 2.0 081 * @see #setPersistenceXmlLocations 082 * @see #setDataSourceLookup 083 * @see #setLoadTimeWeaver 084 * @see org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#setPersistenceUnitManager 085 */ 086public class DefaultPersistenceUnitManager 087 implements PersistenceUnitManager, ResourceLoaderAware, LoadTimeWeaverAware, InitializingBean { 088 089 private static final String CLASS_RESOURCE_PATTERN = "/**/*.class"; 090 091 private static final String PACKAGE_INFO_SUFFIX = ".package-info"; 092 093 private static final String DEFAULT_ORM_XML_RESOURCE = "META-INF/orm.xml"; 094 095 private static final String PERSISTENCE_XML_FILENAME = "persistence.xml"; 096 097 /** 098 * Default location of the {@code persistence.xml} file: 099 * "classpath*:META-INF/persistence.xml". 100 */ 101 public static final String DEFAULT_PERSISTENCE_XML_LOCATION = "classpath*:META-INF/" + PERSISTENCE_XML_FILENAME; 102 103 /** 104 * Default location for the persistence unit root URL: 105 * "classpath:", indicating the root of the classpath. 106 */ 107 public static final String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION = "classpath:"; 108 109 public static final String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME = "default"; 110 111 112 private static final Set<TypeFilter> entityTypeFilters; 113 114 static { 115 entityTypeFilters = new LinkedHashSet<TypeFilter>(4); 116 entityTypeFilters.add(new AnnotationTypeFilter(Entity.class, false)); 117 entityTypeFilters.add(new AnnotationTypeFilter(Embeddable.class, false)); 118 entityTypeFilters.add(new AnnotationTypeFilter(MappedSuperclass.class, false)); 119 try { 120 @SuppressWarnings("unchecked") 121 Class<? extends Annotation> converterAnnotation = (Class<? extends Annotation>) 122 ClassUtils.forName("javax.persistence.Converter", DefaultPersistenceUnitManager.class.getClassLoader()); 123 entityTypeFilters.add(new AnnotationTypeFilter(converterAnnotation, false)); 124 } 125 catch (ClassNotFoundException ex) { 126 // JPA 2.1 API not available 127 } 128 } 129 130 131 protected final Log logger = LogFactory.getLog(getClass()); 132 133 private String[] persistenceXmlLocations = new String[] {DEFAULT_PERSISTENCE_XML_LOCATION}; 134 135 private String defaultPersistenceUnitRootLocation = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION; 136 137 private String defaultPersistenceUnitName = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME; 138 139 private String[] packagesToScan; 140 141 private String[] mappingResources; 142 143 private SharedCacheMode sharedCacheMode; 144 145 private ValidationMode validationMode; 146 147 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); 148 149 private DataSource defaultDataSource; 150 151 private DataSource defaultJtaDataSource; 152 153 private PersistenceUnitPostProcessor[] persistenceUnitPostProcessors; 154 155 private LoadTimeWeaver loadTimeWeaver; 156 157 private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 158 159 private final Set<String> persistenceUnitInfoNames = new HashSet<String>(); 160 161 private final Map<String, PersistenceUnitInfo> persistenceUnitInfos = new HashMap<String, PersistenceUnitInfo>(); 162 163 164 /** 165 * Specify the location of the {@code persistence.xml} files to load. 166 * These can be specified as Spring resource locations and/or location patterns. 167 * <p>Default is "classpath*:META-INF/persistence.xml". 168 */ 169 public void setPersistenceXmlLocation(String persistenceXmlLocation) { 170 this.persistenceXmlLocations = new String[] {persistenceXmlLocation}; 171 } 172 173 /** 174 * Specify multiple locations of {@code persistence.xml} files to load. 175 * These can be specified as Spring resource locations and/or location patterns. 176 * <p>Default is "classpath*:META-INF/persistence.xml". 177 * @param persistenceXmlLocations an array of Spring resource Strings 178 * identifying the location of the {@code persistence.xml} files to read 179 */ 180 public void setPersistenceXmlLocations(String... persistenceXmlLocations) { 181 this.persistenceXmlLocations = persistenceXmlLocations; 182 } 183 184 /** 185 * Set the default persistence unit root location, to be applied 186 * if no unit-specific persistence unit root could be determined. 187 * <p>Default is "classpath:", that is, the root of the current classpath 188 * (nearest root directory). To be overridden if unit-specific resolution 189 * does not work and the classpath root is not appropriate either. 190 */ 191 public void setDefaultPersistenceUnitRootLocation(String defaultPersistenceUnitRootLocation) { 192 this.defaultPersistenceUnitRootLocation = defaultPersistenceUnitRootLocation; 193 } 194 195 /** 196 * Specify the name of the default persistence unit, if any. Default is "default". 197 * <p>Primarily applied to a scanned persistence unit without {@code persistence.xml}. 198 * Also applicable to selecting a default unit from several persistence units available. 199 * @see #setPackagesToScan 200 * @see #obtainDefaultPersistenceUnitInfo 201 */ 202 public void setDefaultPersistenceUnitName(String defaultPersistenceUnitName) { 203 this.defaultPersistenceUnitName = defaultPersistenceUnitName; 204 } 205 206 /** 207 * Set whether to use Spring-based scanning for entity classes in the classpath 208 * instead of using JPA's standard scanning of jar files with {@code persistence.xml} 209 * markers in them. In case of Spring-based scanning, no {@code persistence.xml} 210 * is necessary; all you need to do is to specify base packages to search here. 211 * <p>Default is none. Specify packages to search for autodetection of your entity 212 * classes in the classpath. This is analogous to Spring's component-scan feature 213 * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). 214 * <p>Such package scanning defines a "default persistence unit" in Spring, which 215 * may live next to regularly defined units originating from {@code persistence.xml}. 216 * Its name is determined by {@link #setDefaultPersistenceUnitName}: by default, 217 * it's simply "default". 218 * <p><p>Note: There may be limitations in comparison to regular JPA scanning.</b> 219 * In particular, JPA providers may pick up annotated packages for provider-specific 220 * annotations only when driven by {@code persistence.xml}. As of 4.1, Spring's 221 * scan can detect annotated packages as well if supported by the given 222 * {@link org.springframework.orm.jpa.JpaVendorAdapter} (e.g. for Hibernate). 223 * <p>If no explicit {@link #setMappingResources mapping resources} have been 224 * specified in addition to these packages, this manager looks for a default 225 * {@code META-INF/orm.xml} file in the classpath, registering it as a mapping 226 * resource for the default unit if the mapping file is not co-located with a 227 * {@code persistence.xml} file (in which case we assume it is only meant to be 228 * used with the persistence units defined there, like in standard JPA). 229 * @see #setDefaultPersistenceUnitName 230 * @see #setMappingResources 231 */ 232 public void setPackagesToScan(String... packagesToScan) { 233 this.packagesToScan = packagesToScan; 234 } 235 236 /** 237 * Specify one or more mapping resources (equivalent to {@code <mapping-file>} 238 * entries in {@code persistence.xml}) for the default persistence unit. 239 * Can be used on its own or in combination with entity scanning in the classpath, 240 * in both cases avoiding {@code persistence.xml}. 241 * <p>Note that mapping resources must be relative to the classpath root, 242 * e.g. "META-INF/mappings.xml" or "com/mycompany/repository/mappings.xml", 243 * so that they can be loaded through {@code ClassLoader.getResource}. 244 * <p>If no explicit mapping resources have been specified next to 245 * {@link #setPackagesToScan packages to scan}, this manager looks for a default 246 * {@code META-INF/orm.xml} file in the classpath, registering it as a mapping 247 * resource for the default unit if the mapping file is not co-located with a 248 * {@code persistence.xml} file (in which case we assume it is only meant to be 249 * used with the persistence units defined there, like in standard JPA). 250 * <p>Note that specifying an empty array/list here suppresses the default 251 * {@code META-INF/orm.xml} check. On the other hand, explicitly specifying 252 * {@code META-INF/orm.xml} here will register that file even if it happens 253 * to be co-located with a {@code persistence.xml} file. 254 * @see #setDefaultPersistenceUnitName 255 * @see #setPackagesToScan 256 */ 257 public void setMappingResources(String... mappingResources) { 258 this.mappingResources = mappingResources; 259 } 260 261 /** 262 * Specify the JPA 2.0 shared cache mode for all of this manager's persistence 263 * units, overriding any value in {@code persistence.xml} if set. 264 * @since 4.0 265 * @see javax.persistence.spi.PersistenceUnitInfo#getSharedCacheMode() 266 */ 267 public void setSharedCacheMode(SharedCacheMode sharedCacheMode) { 268 this.sharedCacheMode = sharedCacheMode; 269 } 270 271 /** 272 * Specify the JPA 2.0 validation mode for all of this manager's persistence 273 * units, overriding any value in {@code persistence.xml} if set. 274 * @since 4.0 275 * @see javax.persistence.spi.PersistenceUnitInfo#getValidationMode() 276 */ 277 public void setValidationMode(ValidationMode validationMode) { 278 this.validationMode = validationMode; 279 } 280 281 /** 282 * Specify the JDBC DataSources that the JPA persistence provider is supposed 283 * to use for accessing the database, resolving data source names in 284 * {@code persistence.xml} against Spring-managed DataSources. 285 * <p>The specified Map needs to define data source names for specific DataSource 286 * objects, matching the data source names used in {@code persistence.xml}. 287 * If not specified, data source names will be resolved as JNDI names instead 288 * (as defined by standard JPA). 289 * @see org.springframework.jdbc.datasource.lookup.MapDataSourceLookup 290 */ 291 public void setDataSources(Map<String, DataSource> dataSources) { 292 this.dataSourceLookup = new MapDataSourceLookup(dataSources); 293 } 294 295 /** 296 * Specify the JDBC DataSourceLookup that provides DataSources for the 297 * persistence provider, resolving data source names in {@code persistence.xml} 298 * against Spring-managed DataSource instances. 299 * <p>Default is JndiDataSourceLookup, which resolves DataSource names as 300 * JNDI names (as defined by standard JPA). Specify a BeanFactoryDataSourceLookup 301 * instance if you want DataSource names to be resolved against Spring bean names. 302 * <p>Alternatively, consider passing in a map from names to DataSource instances 303 * via the "dataSources" property. If the {@code persistence.xml} file 304 * does not define DataSource names at all, specify a default DataSource 305 * via the "defaultDataSource" property. 306 * @see org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup 307 * @see org.springframework.jdbc.datasource.lookup.BeanFactoryDataSourceLookup 308 * @see #setDataSources 309 * @see #setDefaultDataSource 310 */ 311 public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { 312 this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()); 313 } 314 315 /** 316 * Return the JDBC DataSourceLookup that provides DataSources for the 317 * persistence provider, resolving data source names in {@code persistence.xml} 318 * against Spring-managed DataSource instances. 319 */ 320 public DataSourceLookup getDataSourceLookup() { 321 return this.dataSourceLookup; 322 } 323 324 /** 325 * Specify the JDBC DataSource that the JPA persistence provider is supposed to use 326 * for accessing the database if none has been specified in {@code persistence.xml}. 327 * This variant indicates no special transaction setup, i.e. typical resource-local. 328 * <p>In JPA speak, a DataSource passed in here will be uses as "nonJtaDataSource" 329 * on the PersistenceUnitInfo passed to the PersistenceProvider, provided that 330 * none has been registered before. 331 * @see javax.persistence.spi.PersistenceUnitInfo#getNonJtaDataSource() 332 */ 333 public void setDefaultDataSource(DataSource defaultDataSource) { 334 this.defaultDataSource = defaultDataSource; 335 } 336 337 /** 338 * Return the JDBC DataSource that the JPA persistence provider is supposed to use 339 * for accessing the database if none has been specified in {@code persistence.xml}. 340 */ 341 public DataSource getDefaultDataSource() { 342 return this.defaultDataSource; 343 } 344 345 /** 346 * Specify the JDBC DataSource that the JPA persistence provider is supposed to use 347 * for accessing the database if none has been specified in {@code persistence.xml}. 348 * This variant indicates that JTA is supposed to be used as transaction type. 349 * <p>In JPA speak, a DataSource passed in here will be uses as "jtaDataSource" 350 * on the PersistenceUnitInfo passed to the PersistenceProvider, provided that 351 * none has been registered before. 352 * @see javax.persistence.spi.PersistenceUnitInfo#getJtaDataSource() 353 */ 354 public void setDefaultJtaDataSource(DataSource defaultJtaDataSource) { 355 this.defaultJtaDataSource = defaultJtaDataSource; 356 } 357 358 /** 359 * Return the JTA-aware DataSource that the JPA persistence provider is supposed to use 360 * for accessing the database if none has been specified in {@code persistence.xml}. 361 */ 362 public DataSource getDefaultJtaDataSource() { 363 return this.defaultJtaDataSource; 364 } 365 366 /** 367 * Set the PersistenceUnitPostProcessors to be applied to each 368 * PersistenceUnitInfo that has been parsed by this manager. 369 * <p>Such post-processors can, for example, register further entity classes and 370 * jar files, in addition to the metadata read from {@code persistence.xml}. 371 */ 372 public void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... postProcessors) { 373 this.persistenceUnitPostProcessors = postProcessors; 374 } 375 376 /** 377 * Return the PersistenceUnitPostProcessors to be applied to each 378 * PersistenceUnitInfo that has been parsed by this manager. 379 */ 380 public PersistenceUnitPostProcessor[] getPersistenceUnitPostProcessors() { 381 return this.persistenceUnitPostProcessors; 382 } 383 384 /** 385 * Specify the Spring LoadTimeWeaver to use for class instrumentation according 386 * to the JPA class transformer contract. 387 * <p>It is not required to specify a LoadTimeWeaver: Most providers will be able 388 * to provide a subset of their functionality without class instrumentation as well, 389 * or operate with their own VM agent specified on JVM startup. Furthermore, 390 * DefaultPersistenceUnitManager falls back to an InstrumentationLoadTimeWeaver 391 * if Spring's agent-based instrumentation is available at runtime. 392 * <p>In terms of Spring-provided weaving options, the most important ones are 393 * InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general) 394 * VM agent specified on JVM startup, and ReflectiveLoadTimeWeaver, which interacts 395 * with an underlying ClassLoader based on specific extended methods being available 396 * on it (for example, interacting with Spring's TomcatInstrumentableClassLoader). 397 * Consider using the {@code context:load-time-weaver} XML tag for creating 398 * such a shared LoadTimeWeaver (autodetecting the environment by default). 399 * @see org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver 400 * @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver 401 */ 402 @Override 403 public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { 404 this.loadTimeWeaver = loadTimeWeaver; 405 } 406 407 /** 408 * Return the Spring LoadTimeWeaver to use for class instrumentation according 409 * to the JPA class transformer contract. 410 */ 411 public LoadTimeWeaver getLoadTimeWeaver() { 412 return this.loadTimeWeaver; 413 } 414 415 @Override 416 public void setResourceLoader(ResourceLoader resourceLoader) { 417 this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); 418 } 419 420 421 @Override 422 public void afterPropertiesSet() { 423 if (this.loadTimeWeaver == null && InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) { 424 this.loadTimeWeaver = new InstrumentationLoadTimeWeaver(this.resourcePatternResolver.getClassLoader()); 425 } 426 preparePersistenceUnitInfos(); 427 } 428 429 /** 430 * Prepare the PersistenceUnitInfos according to the configuration 431 * of this manager: scanning for {@code persistence.xml} files, 432 * parsing all matching files, configuring and post-processing them. 433 * <p>PersistenceUnitInfos cannot be obtained before this preparation 434 * method has been invoked. 435 * @see #obtainDefaultPersistenceUnitInfo() 436 * @see #obtainPersistenceUnitInfo(String) 437 */ 438 public void preparePersistenceUnitInfos() { 439 this.persistenceUnitInfoNames.clear(); 440 this.persistenceUnitInfos.clear(); 441 442 List<SpringPersistenceUnitInfo> puis = readPersistenceUnitInfos(); 443 for (SpringPersistenceUnitInfo pui : puis) { 444 if (pui.getPersistenceUnitRootUrl() == null) { 445 pui.setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl()); 446 } 447 if (pui.getJtaDataSource() == null) { 448 pui.setJtaDataSource(this.defaultJtaDataSource); 449 } 450 if (pui.getNonJtaDataSource() == null) { 451 pui.setNonJtaDataSource(this.defaultDataSource); 452 } 453 if (this.sharedCacheMode != null) { 454 pui.setSharedCacheMode(this.sharedCacheMode); 455 } 456 if (this.validationMode != null) { 457 pui.setValidationMode(this.validationMode); 458 } 459 if (this.loadTimeWeaver != null) { 460 pui.init(this.loadTimeWeaver); 461 } 462 else { 463 pui.init(this.resourcePatternResolver.getClassLoader()); 464 } 465 postProcessPersistenceUnitInfo(pui); 466 String name = pui.getPersistenceUnitName(); 467 if (!this.persistenceUnitInfoNames.add(name) && !isPersistenceUnitOverrideAllowed()) { 468 StringBuilder msg = new StringBuilder(); 469 msg.append("Conflicting persistence unit definitions for name '").append(name).append("': "); 470 msg.append(pui.getPersistenceUnitRootUrl()).append(", "); 471 msg.append(this.persistenceUnitInfos.get(name).getPersistenceUnitRootUrl()); 472 throw new IllegalStateException(msg.toString()); 473 } 474 this.persistenceUnitInfos.put(name, pui); 475 } 476 } 477 478 /** 479 * Read all persistence unit infos from {@code persistence.xml}, 480 * as defined in the JPA specification. 481 */ 482 private List<SpringPersistenceUnitInfo> readPersistenceUnitInfos() { 483 List<SpringPersistenceUnitInfo> infos = new LinkedList<SpringPersistenceUnitInfo>(); 484 String defaultName = this.defaultPersistenceUnitName; 485 boolean buildDefaultUnit = (this.packagesToScan != null || this.mappingResources != null); 486 boolean foundDefaultUnit = false; 487 488 PersistenceUnitReader reader = new PersistenceUnitReader(this.resourcePatternResolver, this.dataSourceLookup); 489 SpringPersistenceUnitInfo[] readInfos = reader.readPersistenceUnitInfos(this.persistenceXmlLocations); 490 for (SpringPersistenceUnitInfo readInfo : readInfos) { 491 infos.add(readInfo); 492 if (defaultName != null && defaultName.equals(readInfo.getPersistenceUnitName())) { 493 foundDefaultUnit = true; 494 } 495 } 496 497 if (buildDefaultUnit) { 498 if (foundDefaultUnit) { 499 if (logger.isInfoEnabled()) { 500 logger.info("Found explicit default unit with name '" + defaultName + "' in persistence.xml - " + 501 "overriding local default unit settings ('packagesToScan'/'mappingResources')"); 502 } 503 } 504 else { 505 infos.add(buildDefaultPersistenceUnitInfo()); 506 } 507 } 508 return infos; 509 } 510 511 /** 512 * Perform Spring-based scanning for entity classes. 513 * @see #setPackagesToScan 514 */ 515 private SpringPersistenceUnitInfo buildDefaultPersistenceUnitInfo() { 516 SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo(); 517 scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName); 518 scannedUnit.setExcludeUnlistedClasses(true); 519 520 if (this.packagesToScan != null) { 521 for (String pkg : this.packagesToScan) { 522 try { 523 String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + 524 ClassUtils.convertClassNameToResourcePath(pkg) + CLASS_RESOURCE_PATTERN; 525 Resource[] resources = this.resourcePatternResolver.getResources(pattern); 526 MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); 527 for (Resource resource : resources) { 528 if (resource.isReadable()) { 529 MetadataReader reader = readerFactory.getMetadataReader(resource); 530 String className = reader.getClassMetadata().getClassName(); 531 if (matchesFilter(reader, readerFactory)) { 532 scannedUnit.addManagedClassName(className); 533 if (scannedUnit.getPersistenceUnitRootUrl() == null) { 534 URL url = resource.getURL(); 535 if (ResourceUtils.isJarURL(url)) { 536 scannedUnit.setPersistenceUnitRootUrl(ResourceUtils.extractJarFileURL(url)); 537 } 538 } 539 } 540 else if (className.endsWith(PACKAGE_INFO_SUFFIX)) { 541 scannedUnit.addManagedPackage( 542 className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length())); 543 } 544 } 545 } 546 } 547 catch (IOException ex) { 548 throw new PersistenceException("Failed to scan classpath for unlisted entity classes", ex); 549 } 550 } 551 } 552 553 if (this.mappingResources != null) { 554 for (String mappingFileName : this.mappingResources) { 555 scannedUnit.addMappingFileName(mappingFileName); 556 } 557 } 558 else { 559 Resource ormXml = getOrmXmlForDefaultPersistenceUnit(); 560 if (ormXml != null) { 561 scannedUnit.addMappingFileName(DEFAULT_ORM_XML_RESOURCE); 562 if (scannedUnit.getPersistenceUnitRootUrl() == null) { 563 try { 564 scannedUnit.setPersistenceUnitRootUrl( 565 PersistenceUnitReader.determinePersistenceUnitRootUrl(ormXml)); 566 } 567 catch (IOException ex) { 568 logger.debug("Failed to determine persistence unit root URL from orm.xml location", ex); 569 } 570 } 571 } 572 } 573 574 return scannedUnit; 575 } 576 577 /** 578 * Check whether any of the configured entity type filters matches 579 * the current class descriptor contained in the metadata reader. 580 */ 581 private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { 582 for (TypeFilter filter : entityTypeFilters) { 583 if (filter.match(reader, readerFactory)) { 584 return true; 585 } 586 } 587 return false; 588 } 589 590 /** 591 * Try to determine the persistence unit root URL based on the given 592 * "defaultPersistenceUnitRootLocation". 593 * @return the persistence unit root URL to pass to the JPA PersistenceProvider 594 * @see #setDefaultPersistenceUnitRootLocation 595 */ 596 private URL determineDefaultPersistenceUnitRootUrl() { 597 if (this.defaultPersistenceUnitRootLocation == null) { 598 return null; 599 } 600 try { 601 URL url = this.resourcePatternResolver.getResource(this.defaultPersistenceUnitRootLocation).getURL(); 602 return (ResourceUtils.isJarURL(url) ? ResourceUtils.extractJarFileURL(url) : url); 603 } 604 catch (IOException ex) { 605 throw new PersistenceException("Unable to resolve persistence unit root URL", ex); 606 } 607 } 608 609 /** 610 * Determine JPA's default "META-INF/orm.xml" resource for use with Spring's default 611 * persistence unit, if any. 612 * <p>Checks whether a "META-INF/orm.xml" file exists in the classpath and uses it 613 * if it is not co-located with a "META-INF/persistence.xml" file. 614 */ 615 private Resource getOrmXmlForDefaultPersistenceUnit() { 616 Resource ormXml = this.resourcePatternResolver.getResource( 617 this.defaultPersistenceUnitRootLocation + DEFAULT_ORM_XML_RESOURCE); 618 if (ormXml.exists()) { 619 try { 620 Resource persistenceXml = ormXml.createRelative(PERSISTENCE_XML_FILENAME); 621 if (!persistenceXml.exists()) { 622 return ormXml; 623 } 624 } 625 catch (IOException ex) { 626 // Cannot resolve relative persistence.xml file - let's assume it's not there. 627 return ormXml; 628 } 629 } 630 return null; 631 } 632 633 634 /** 635 * Return the specified PersistenceUnitInfo from this manager's cache 636 * of processed persistence units, keeping it in the cache (i.e. not 637 * 'obtaining' it for use but rather just accessing it for post-processing). 638 * <p>This can be used in {@link #postProcessPersistenceUnitInfo} implementations, 639 * detecting existing persistence units of the same name and potentially merging them. 640 * @param persistenceUnitName the name of the desired persistence unit 641 * @return the PersistenceUnitInfo in mutable form, or {@code null} if not available 642 */ 643 protected final MutablePersistenceUnitInfo getPersistenceUnitInfo(String persistenceUnitName) { 644 PersistenceUnitInfo pui = this.persistenceUnitInfos.get(persistenceUnitName); 645 return (MutablePersistenceUnitInfo) pui; 646 } 647 648 /** 649 * Hook method allowing subclasses to customize each PersistenceUnitInfo. 650 * <p>The default implementation delegates to all registered PersistenceUnitPostProcessors. 651 * It is usually preferable to register further entity classes, jar files etc there 652 * rather than in a subclass of this manager, to be able to reuse the post-processors. 653 * @param pui the chosen PersistenceUnitInfo, as read from {@code persistence.xml}. 654 * Passed in as MutablePersistenceUnitInfo. 655 * @see #setPersistenceUnitPostProcessors 656 */ 657 protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) { 658 PersistenceUnitPostProcessor[] postProcessors = getPersistenceUnitPostProcessors(); 659 if (postProcessors != null) { 660 for (PersistenceUnitPostProcessor postProcessor : postProcessors) { 661 postProcessor.postProcessPersistenceUnitInfo(pui); 662 } 663 } 664 } 665 666 /** 667 * Return whether an override of a same-named persistence unit is allowed. 668 * <p>Default is {@code false}. May be overridden to return {@code true}, 669 * for example if {@link #postProcessPersistenceUnitInfo} is able to handle that case. 670 */ 671 protected boolean isPersistenceUnitOverrideAllowed() { 672 return false; 673 } 674 675 676 @Override 677 public PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() { 678 if (this.persistenceUnitInfoNames.isEmpty()) { 679 throw new IllegalStateException("No persistence units parsed from " + 680 ObjectUtils.nullSafeToString(this.persistenceXmlLocations)); 681 } 682 if (this.persistenceUnitInfos.isEmpty()) { 683 throw new IllegalStateException("All persistence units from " + 684 ObjectUtils.nullSafeToString(this.persistenceXmlLocations) + " already obtained"); 685 } 686 if (this.persistenceUnitInfos.size() > 1) { 687 return obtainPersistenceUnitInfo(this.defaultPersistenceUnitName); 688 } 689 PersistenceUnitInfo pui = this.persistenceUnitInfos.values().iterator().next(); 690 this.persistenceUnitInfos.clear(); 691 return pui; 692 } 693 694 @Override 695 public PersistenceUnitInfo obtainPersistenceUnitInfo(String persistenceUnitName) { 696 PersistenceUnitInfo pui = this.persistenceUnitInfos.remove(persistenceUnitName); 697 if (pui == null) { 698 if (!this.persistenceUnitInfoNames.contains(persistenceUnitName)) { 699 throw new IllegalArgumentException( 700 "No persistence unit with name '" + persistenceUnitName + "' found"); 701 } 702 else { 703 throw new IllegalStateException( 704 "Persistence unit with name '" + persistenceUnitName + "' already obtained"); 705 } 706 } 707 return pui; 708 } 709 710}