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}