DATAJPA-444 - Improve detection of PersistenceProvider implementations.
Since the location of the Hibernate EntityManager implementation changed in Hibernate 4.3 to org.hibernate.jpa.HibernateEntityManager, we now support org.hibernate.jpa.HibernateEntityManager as well as org.hibernate.ejb.HibernateEntityManager as a Hibernate PersistenceProvider. Original pull request: #55.
This commit is contained in:
committed by
Christoph Strobl
parent
44994ff3e9
commit
5e8928f54f
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2013 the original author or authors.
|
||||
* Copyright 2008-2014 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.
|
||||
@@ -17,6 +17,9 @@ package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static org.springframework.data.jpa.repository.utils.JpaClassUtils.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
|
||||
@@ -27,16 +30,22 @@ import org.springframework.data.jpa.repository.query.QueryExtractor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Enumeration representing peristence providers to be used.
|
||||
* Enumeration representing persistence providers to be used.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public enum PersistenceProvider implements QueryExtractor {
|
||||
|
||||
/**
|
||||
* Hibernate persistence provider.
|
||||
* <p>
|
||||
* Since Hibernate 4.3 the location of the HibernateEntityManager moved to the org.hibernate.jpa package. In order to
|
||||
* support both locations we interpret both classnames as a Hibernate {@code PersistenceProvider}.
|
||||
*
|
||||
* @see DATAJPA-444
|
||||
*/
|
||||
HIBERNATE("org.hibernate.ejb.HibernateEntityManager") {
|
||||
HIBERNATE(Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE, Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE) {
|
||||
|
||||
public String extractQueryString(Query query) {
|
||||
|
||||
@@ -60,7 +69,7 @@ public enum PersistenceProvider implements QueryExtractor {
|
||||
/**
|
||||
* EclipseLink persistence provider.
|
||||
*/
|
||||
ECLIPSELINK("org.eclipse.persistence.jpa.JpaEntityManager") {
|
||||
ECLIPSELINK(Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE) {
|
||||
|
||||
public String extractQueryString(Query query) {
|
||||
|
||||
@@ -72,7 +81,7 @@ public enum PersistenceProvider implements QueryExtractor {
|
||||
/**
|
||||
* OpenJpa persistence provider.
|
||||
*/
|
||||
OPEN_JPA("org.apache.openjpa.persistence.OpenJPAEntityManager") {
|
||||
OPEN_JPA(Constants.OPENJPA_ENTITY_MANAGER_INTERFACE) {
|
||||
|
||||
public String extractQueryString(Query query) {
|
||||
|
||||
@@ -83,7 +92,7 @@ public enum PersistenceProvider implements QueryExtractor {
|
||||
/**
|
||||
* Unknown special provider. Use standard JPA.
|
||||
*/
|
||||
GENERIC_JPA("javax.persistence.EntityManager") {
|
||||
GENERIC_JPA(Constants.GENERIC_JPA_ENTITY_MANAGER_INTERFACE) {
|
||||
|
||||
public String extractQueryString(Query query) {
|
||||
|
||||
@@ -97,16 +106,33 @@ public enum PersistenceProvider implements QueryExtractor {
|
||||
}
|
||||
};
|
||||
|
||||
private String entityManagerClassName;
|
||||
/**
|
||||
* Holds the PersistenceProvider specific interface names.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
static interface Constants {
|
||||
|
||||
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "javax.persistence.EntityManager";
|
||||
String OPENJPA_ENTITY_MANAGER_INTERFACE = "org.apache.openjpa.persistence.OpenJPAEntityManager";
|
||||
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
|
||||
String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.ejb.HibernateEntityManager";
|
||||
String HIBERNATE43_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager";
|
||||
}
|
||||
|
||||
private List<String> entityManagerClassNames;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistenceProvider}.
|
||||
*
|
||||
* @param entityManagerClassName the name of the provider specific {@link EntityManager} implementation
|
||||
* @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not
|
||||
* be {@literal null} or empty.
|
||||
*/
|
||||
private PersistenceProvider(String entityManagerClassName) {
|
||||
private PersistenceProvider(String... entityManagerClassNames) {
|
||||
|
||||
this.entityManagerClassName = entityManagerClassName;
|
||||
Assert.notEmpty(entityManagerClassNames, "EntityManagerClassNames must not be empty!");
|
||||
|
||||
this.entityManagerClassNames = Arrays.asList(entityManagerClassNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,8 +147,11 @@ public enum PersistenceProvider implements QueryExtractor {
|
||||
Assert.notNull(em);
|
||||
|
||||
for (PersistenceProvider provider : values()) {
|
||||
if (isEntityManagerOfType(em, provider.entityManagerClassName)) {
|
||||
return provider;
|
||||
for (String entityManagerClassName : provider.entityManagerClassNames) {
|
||||
|
||||
if (isEntityManagerOfType(em, entityManagerClassName)) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.asm.ClassWriter;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.instrument.classloading.ShadowingClassLoader;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Tests for PersistenceProvider detection logic in {@link PersistenceProvider}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class PersistenceProviderTests {
|
||||
|
||||
private ShadowingClassLoader shadowingClassLoader;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
shadowingClassLoader = new ShadowingClassLoader(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-444
|
||||
*/
|
||||
@Test
|
||||
public void detectsHibernatePersistenceProviderForHibernateVersionLessThan4dot3() throws Exception {
|
||||
|
||||
shadowingClassLoader.excludePackage("org.hibernate");
|
||||
|
||||
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE);
|
||||
|
||||
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.HIBERNATE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-444
|
||||
*/
|
||||
@Test
|
||||
public void detectsHibernatePersistenceProviderForHibernateVersionGreaterEqual4dot3() throws Exception {
|
||||
|
||||
shadowingClassLoader.excludePackage("org.hibernate");
|
||||
|
||||
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE);
|
||||
|
||||
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.HIBERNATE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsOpenJpaPersistenceProvider() throws Exception {
|
||||
|
||||
shadowingClassLoader.excludePackage("org.apache.openjpa.persistence");
|
||||
|
||||
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.OPENJPA_ENTITY_MANAGER_INTERFACE);
|
||||
|
||||
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.OPEN_JPA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsEclipseLinkPersistenceProvider() throws Exception {
|
||||
|
||||
shadowingClassLoader.excludePackage("org.eclipse.persistence.jpa");
|
||||
|
||||
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE);
|
||||
|
||||
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.ECLIPSELINK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallbackToGenericJpaForUnknownPersistenceProvider() throws Exception {
|
||||
|
||||
EntityManager em = mockProviderSpecificEntityManagerInterface("foo.bar.unknown.jpa.JpaEntityManager");
|
||||
|
||||
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.GENERIC_JPA));
|
||||
}
|
||||
|
||||
private EntityManager mockProviderSpecificEntityManagerInterface(String interfaceName) throws ClassNotFoundException {
|
||||
|
||||
Class<?> providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader,
|
||||
EntityManager.class);
|
||||
|
||||
EntityManager em = EntityManager.class.cast(Mockito.mock(providerSpecificEntityManagerInterface));
|
||||
Mockito.when(em.getDelegate()).thenReturn(em); // delegate is used to determine the classloader of the provider
|
||||
// specific interface, therefore we return the proxied
|
||||
// EntityManager.
|
||||
|
||||
return em;
|
||||
}
|
||||
|
||||
static class InterfaceGenerator implements Opcodes {
|
||||
|
||||
public static Class<?> generate(final String interfaceName, ClassLoader parentClassLoader,
|
||||
final Class<?>... interfaces) throws ClassNotFoundException {
|
||||
|
||||
class CustomClassLoader extends ClassLoader {
|
||||
|
||||
public CustomClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
|
||||
if (name.equals(interfaceName)) {
|
||||
|
||||
byte[] byteCode = generateByteCodeForInterface(interfaceName, interfaces);
|
||||
return defineClass(name, byteCode, 0, byteCode.length);
|
||||
}
|
||||
|
||||
return super.findClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
return new CustomClassLoader(parentClassLoader).loadClass(interfaceName);
|
||||
}
|
||||
|
||||
private static byte[] generateByteCodeForInterface(final String interfaceName, Class<?>... interfacesToImplement) {
|
||||
|
||||
String interfaceResourcePath = ClassUtils.convertClassNameToResourcePath(interfaceName);
|
||||
|
||||
ClassWriter cw = new ClassWriter(0);
|
||||
cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, interfaceResourcePath, null, "java/lang/Object",
|
||||
toResourcePaths(interfacesToImplement));
|
||||
cw.visitSource(interfaceResourcePath + ".java", null);
|
||||
cw.visitEnd();
|
||||
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private static String[] toResourcePaths(Class<?>... interfacesToImplement) {
|
||||
|
||||
List<String> interfaceResourcePaths = new ArrayList<String>(interfacesToImplement.length);
|
||||
for (Class<?> iface : interfacesToImplement) {
|
||||
interfaceResourcePaths.add(ClassUtils.convertClassNameToResourcePath(iface.getName()));
|
||||
}
|
||||
|
||||
return interfaceResourcePaths.toArray(new String[interfaceResourcePaths.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user