I did a quick spike just to see if I will be able to achive a unified and extensible persistence unit. Here is what I did:
1. I modified LocalContainerEntityManagerBeanFactory to provide a hook method for creating the PersistenceUnitReader
Code:
private SpringPersistenceUnitInfo parsePersistenceUnitInfo() {
PersistenceUnitReader reader = createPersistenceUnitReader(this.resourceLoader, this.dataSourceLookup);
...
}
protected PersistenceUnitReader createPersistenceUnitReader(ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) {
return new PersistenceUnitReader(resourceLoader, dataSourceLookup);
}
I had to change both classes PersistenceUnitReader and SpringPersistenceUnitInfo from package to public scope.
2. Then I created MultiFilesystemClasspathLocalContainerEntityManage rFactoryBean
Code:
public class MultiFilesystemClasspathLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {
private String classpathMarkerFile;
public void setClasspathMarkerFile(String rootPersistenceXmlLocation) {
this.classpathMarkerFile = rootPersistenceXmlLocation;
}
@Override
protected PersistenceUnitReader createPersistenceUnitReader(ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) {
return new MultiFilesystemClasspathHibernatePersistenceUnitReader(classpathMarkerFile, resourceLoader, dataSourceLookup);
}
}
3. MultiFilesystemClasspathHibernatePersistenceUnitRe ader does the actual work:
Code:
public class MultiFilesystemClasspathHibernatePersistenceUnitReader extends PersistenceUnitReader {
private final String classpathMarkerFile;
private final PathMatchingResourcePatternResolver resourcePatternResolver;
public MultiFilesystemClasspathHibernatePersistenceUnitReader(String classpathMarkerFile, ResourceLoader resourceLoader, DataSourceLookup dataSourceLookup) {
super(resourceLoader, dataSourceLookup);
this.classpathMarkerFile = classpathMarkerFile;
this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
}
@Override
public SpringPersistenceUnitInfo[] readPersistenceUnitInfos(String persistenceXmlLocation) {
List<URL> classpathRoots = new ArrayList<URL>();
Resource[] resources = null;
try {
resources = resourcePatternResolver.getResources("classpath*:"+classpathMarkerFile);
for (Resource resource : resources) {
if (resource.getURL().getProtocol().equals("file")) {
URL classpathRoot = new URL("file:"+determineClasspathRootDir(resource, classpathMarkerFile));
classpathRoots.add(classpathRoot);
}
}
}
catch (IOException ex) {
throw new IllegalArgumentException("Cannot resolve classpaths roots, marker=" + classpathMarkerFile, ex);
}
SpringPersistenceUnitInfo[] infos = super.readPersistenceUnitInfos(persistenceXmlLocation);
if (infos.length > 1) {
throw new IllegalArgumentException("Found more than one persistence unit: " +
"persistenceXmlLocation=" + persistenceXmlLocation +
"units=" + infos);
} else if (infos.length == 0) {
throw new IllegalArgumentException("Could find the persistence unit: " +
"persistenceXmlLocation=" + persistenceXmlLocation);
}
for (URL classpathRoot : classpathRoots) {
infos[0].addJarFileUrl(classpathRoot);
}
return infos;
}
protected String determineClasspathRootDir(Resource resource, String classpathMarkerFile) throws IOException {
String location = resource.getFile().getAbsolutePath();
return location.substring(0, location.length()-classpathMarkerFile.length());
}
}
The result is a PersistenceUnit returned to LCEMFB which has all the classpath roots added to the PU as jars. This works with hibernate because they have generic class scanner. It may not work with other JPA providers.
I made changes to the persistenceUnitRootUrl since there is no way to support multiple roots.
4. Here is the "system/unified" EMFB bean definition:
Code:
<bean id="persistence.entityManagerFactory" class="org.opentrader.infra.spring.MultiFilesystemClasspathLocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="persistence.dataSource"/>
<property name="classpathMarkerFile" value="META-INF/component.ctx.xml"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence-unit.xml"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
...
</property>
...
</bean>
Any classpath that contains a the META-INF/component.ctx.xml will be added to the PU. But any file could have been used as the marker. I didn't use persistence.xml since I don't want more than one PU/EMF created. But clearly multiple LCEMFB instances could be defined with different marker files (or some other classpath selection strategy) and hence multiple EMF is possible.
5. The unfied persistence-unit.xml is just to satisfy the spec, as I want to define everything possible in the spring context file:
Code:
<persistence ...>
<persistence-unit name="OpenTrader" transaction-type="JTA"/>
</persistence>
----
This seems to work pretty good. Is the change I made to LCEMFB what you had in mind Costin? Or were you going to provide an interface for a PersistenceUnitReaderCreator or some such? It would be nice not to have to subclass LCEMFB as well as the PUR.
Do you see anything illegal about what I am doing? I could make the PersistenceUnitReader subclass more generic so it uses a strategy to determine the classpath roots to add as the jar URLs.
In the case where where a provider does not accept file: URLs for the jar: URLs like hibernate does, the PersistenceUnitReader would probably need to scan the file system itself and set the classes using PU.addManagedClassName(...).
Any feedback would be much appreciated. Oh, I'm really digging the spring JPA support. Its much cleaner than what I was doing to bootstrap hibernate and get it install all the EJB3 listeners. I also killed a whole test setup framework in place of AbstractJpaTests, which is working great.
cheers