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}