diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java index 6f6fd9219d..829d4703f6 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.java @@ -120,7 +120,24 @@ public class LocalContainerEntityManagerFactoryBean extends AbstractEntityManage * @see #setPersistenceUnitManager */ public void setPersistenceXmlLocation(String persistenceXmlLocation) { - this.internalPersistenceUnitManager.setPersistenceXmlLocations(new String[] {persistenceXmlLocation}); + this.internalPersistenceUnitManager.setPersistenceXmlLocation(persistenceXmlLocation); + } + + /** + * Set whether to use Spring-based scanning for entity classes in the classpath + * instead of using JPA's standard scanning of jar files with persistence.xml + * markers in them. In case of Spring-based scanning, no persistence.xml + * is necessary; all you need to do is to specify base packages to search here. + *

Default is none. Specify packages to search for autodetection of your entity + * classes in the classpath. This is analogous to Spring's component-scan feature + * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). + *

NOTE: Only applied if no external PersistenceUnitManager specified. + * @param packagesToScan one or more base packages to search, analogous to + * Spring's component-scan configuration for regular Spring components + * @see #setPersistenceUnitManager + */ + public void setPackagesToScan(String[] packagesToScan) { + this.internalPersistenceUnitManager.setPackagesToScan(packagesToScan); } /** diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java index 5dc25c6e91..e25f62dd34 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/persistenceunit/DefaultPersistenceUnitManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,16 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.MappedSuperclass; import javax.persistence.PersistenceException; import javax.persistence.spi.PersistenceUnitInfo; import javax.sql.DataSource; @@ -37,6 +43,11 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; import org.springframework.instrument.classloading.LoadTimeWeaver; import org.springframework.jdbc.datasource.lookup.DataSourceLookup; @@ -80,13 +91,26 @@ public class DefaultPersistenceUnitManager */ public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION = "classpath:"; + public final static String ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME = "default"; + + private static final String RESOURCE_PATTERN = "/**/*.class"; + private static final boolean jpa2ApiPresent = ClassUtils.hasMethod(PersistenceUnitInfo.class, "getSharedCacheMode"); + private static final TypeFilter[] entityTypeFilters = new TypeFilter[] { + new AnnotationTypeFilter(Entity.class, false), + new AnnotationTypeFilter(Embeddable.class, false), + new AnnotationTypeFilter(MappedSuperclass.class, false)}; + private String[] persistenceXmlLocations = new String[] {DEFAULT_PERSISTENCE_XML_LOCATION}; private String defaultPersistenceUnitRootLocation = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_ROOT_LOCATION; + private String defaultPersistenceUnitName = ORIGINAL_DEFAULT_PERSISTENCE_UNIT_NAME; + + private String[] packagesToScan; + private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private DataSource defaultDataSource; @@ -133,6 +157,30 @@ public class DefaultPersistenceUnitManager this.defaultPersistenceUnitRootLocation = defaultPersistenceUnitRootLocation; } + /** + * Specify the name of the default persistence unit, if any. Default is "default". + *

Primarily applied to a scanned persistence unit without persistence.xml. + * Also applicable to selecting a default unit from several persistence units available. + * @see #setPackagesToScan + * @see #obtainDefaultPersistenceUnitInfo + */ + public void setDefaultPersistenceUnitName(String defaultPersistenceUnitName) { + this.defaultPersistenceUnitName = defaultPersistenceUnitName; + } + + /** + * Set whether to use Spring-based scanning for entity classes in the classpath + * instead of using JPA's standard scanning of jar files with persistence.xml + * markers in them. In case of Spring-based scanning, no persistence.xml + * is necessary; all you need to do is to specify base packages to search here. + *

Default is none. Specify packages to search for autodetection of your entity + * classes in the classpath. This is analogous to Spring's component-scan feature + * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). + */ + public void setPackagesToScan(String[] packagesToScan) { + this.packagesToScan = packagesToScan; + } + /** * Specify the JDBC DataSources that the JPA persistence provider is supposed * to use for accessing the database, resolving data source names in @@ -273,7 +321,7 @@ public class DefaultPersistenceUnitManager public void preparePersistenceUnitInfos() { this.persistenceUnitInfoNames.clear(); this.persistenceUnitInfos.clear(); - SpringPersistenceUnitInfo[] puis = readPersistenceUnitInfos(); + List puis = readPersistenceUnitInfos(); for (SpringPersistenceUnitInfo pui : puis) { if (pui.getPersistenceUnitRootUrl() == null) { pui.setPersistenceUnitRootUrl(determineDefaultPersistenceUnitRootUrl()); @@ -289,7 +337,13 @@ public class DefaultPersistenceUnitManager } postProcessPersistenceUnitInfo(pui); String name = pui.getPersistenceUnitName(); - this.persistenceUnitInfoNames.add(name); + if (!this.persistenceUnitInfoNames.add(name)) { + StringBuilder msg = new StringBuilder(); + msg.append("Conflicting persistence unit definitions for name '").append(name).append("': "); + msg.append(pui.getPersistenceUnitRootUrl()).append(", "); + msg.append(this.persistenceUnitInfos.get(name).getPersistenceUnitRootUrl()); + throw new IllegalStateException(msg.toString()); + } PersistenceUnitInfo puiToStore = pui; if (jpa2ApiPresent) { puiToStore = (PersistenceUnitInfo) Proxy.newProxyInstance(SmartPersistenceUnitInfo.class.getClassLoader(), @@ -303,9 +357,58 @@ public class DefaultPersistenceUnitManager * Read all persistence unit infos from persistence.xml, * as defined in the JPA specification. */ - private SpringPersistenceUnitInfo[] readPersistenceUnitInfos() { + private List readPersistenceUnitInfos() { PersistenceUnitReader reader = new PersistenceUnitReader(this.resourcePatternResolver, this.dataSourceLookup); - return reader.readPersistenceUnitInfos(this.persistenceXmlLocations); + List infos = new LinkedList(); + infos.addAll(Arrays.asList(reader.readPersistenceUnitInfos(this.persistenceXmlLocations))); + if (this.packagesToScan != null) { + infos.add(scanPackages()); + } + return infos; + } + + /** + * Perform Spring-based scanning for entity classes. + * @see #setPackagesToScan + */ + private SpringPersistenceUnitInfo scanPackages() { + SpringPersistenceUnitInfo scannedUnit = new SpringPersistenceUnitInfo(); + try { + scannedUnit.setPersistenceUnitName(this.defaultPersistenceUnitName); + scannedUnit.excludeUnlistedClasses(); + for (String pkg : this.packagesToScan) { + String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN; + Resource[] resources = this.resourcePatternResolver.getResources(pattern); + MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); + for (Resource resource : resources) { + if (resource.isReadable()) { + MetadataReader reader = readerFactory.getMetadataReader(resource); + String className = reader.getClassMetadata().getClassName(); + if (matchesFilter(reader, readerFactory)) { + scannedUnit.addManagedClassName(className); + } + } + } + } + return scannedUnit; + } + catch (IOException ex) { + throw new PersistenceException("Failed to scan classpath for unlisted classes", ex); + } + } + + /** + * Check whether any of the configured entity type filters matches + * the current class descriptor contained in the metadata reader. + */ + private boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { + for (TypeFilter filter : entityTypeFilters) { + if (filter.match(reader, readerFactory)) { + return true; + } + } + return false; } /** @@ -327,6 +430,7 @@ public class DefaultPersistenceUnitManager } } + /** * Return the specified PersistenceUnitInfo from this manager's cache * of processed persistence units, keeping it in the cache (i.e. not @@ -378,8 +482,7 @@ public class DefaultPersistenceUnitManager ObjectUtils.nullSafeToString(this.persistenceXmlLocations) + " already obtained"); } if (this.persistenceUnitInfos.size() > 1) { - throw new IllegalStateException("No single default persistence unit defined in " + - ObjectUtils.nullSafeToString(this.persistenceXmlLocations)); + return obtainPersistenceUnitInfo(this.defaultPersistenceUnitName); } PersistenceUnitInfo pui = this.persistenceUnitInfos.values().iterator().next(); this.persistenceUnitInfos.clear();