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