Skip to content

Commit 659c6d1

Browse files
Thomas Darimontchristophstrobl
Thomas Darimont
authored andcommitted
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.
1 parent 3e8cccd commit 659c6d1

File tree

2 files changed

+205
-12
lines changed

2 files changed

+205
-12
lines changed

src/main/java/org/springframework/data/jpa/repository/support/PersistenceProvider.java

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2013 the original author or authors.
2+
* Copyright 2008-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
1717

1818
import static org.springframework.data.jpa.repository.utils.JpaClassUtils.*;
1919

20+
import java.util.Arrays;
21+
import java.util.List;
22+
2023
import javax.persistence.EntityManager;
2124
import javax.persistence.Query;
2225

@@ -27,16 +30,22 @@
2730
import org.springframework.util.Assert;
2831

2932
/**
30-
* Enumeration representing peristence providers to be used.
33+
* Enumeration representing persistence providers to be used.
3134
*
3235
* @author Oliver Gierke
36+
* @author Thomas Darimont
3337
*/
3438
public enum PersistenceProvider implements QueryExtractor {
3539

3640
/**
3741
* Hibernate persistence provider.
42+
* <p>
43+
* Since Hibernate 4.3 the location of the HibernateEntityManager moved to the org.hibernate.jpa package. In order to
44+
* support both locations we interpret both classnames as a Hibernate {@code PersistenceProvider}.
45+
*
46+
* @see DATAJPA-444
3847
*/
39-
HIBERNATE("org.hibernate.ejb.HibernateEntityManager") {
48+
HIBERNATE(Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE, Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE) {
4049

4150
public String extractQueryString(Query query) {
4251

@@ -60,7 +69,7 @@ protected String getCountQueryPlaceholder() {
6069
/**
6170
* EclipseLink persistence provider.
6271
*/
63-
ECLIPSELINK("org.eclipse.persistence.jpa.JpaEntityManager") {
72+
ECLIPSELINK(Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE) {
6473

6574
public String extractQueryString(Query query) {
6675

@@ -72,7 +81,7 @@ public String extractQueryString(Query query) {
7281
/**
7382
* OpenJpa persistence provider.
7483
*/
75-
OPEN_JPA("org.apache.openjpa.persistence.OpenJPAEntityManager") {
84+
OPEN_JPA(Constants.OPENJPA_ENTITY_MANAGER_INTERFACE) {
7685

7786
public String extractQueryString(Query query) {
7887

@@ -83,7 +92,7 @@ public String extractQueryString(Query query) {
8392
/**
8493
* Unknown special provider. Use standard JPA.
8594
*/
86-
GENERIC_JPA("javax.persistence.EntityManager") {
95+
GENERIC_JPA(Constants.GENERIC_JPA_ENTITY_MANAGER_INTERFACE) {
8796

8897
public String extractQueryString(Query query) {
8998

@@ -97,16 +106,33 @@ public boolean canExtractQuery() {
97106
}
98107
};
99108

100-
private String entityManagerClassName;
109+
/**
110+
* Holds the PersistenceProvider specific interface names.
111+
*
112+
* @author Thomas Darimont
113+
*/
114+
static interface Constants {
115+
116+
String GENERIC_JPA_ENTITY_MANAGER_INTERFACE = "javax.persistence.EntityManager";
117+
String OPENJPA_ENTITY_MANAGER_INTERFACE = "org.apache.openjpa.persistence.OpenJPAEntityManager";
118+
String ECLIPSELINK_ENTITY_MANAGER_INTERFACE = "org.eclipse.persistence.jpa.JpaEntityManager";
119+
String HIBERNATE_ENTITY_MANAGER_INTERFACE = "org.hibernate.ejb.HibernateEntityManager";
120+
String HIBERNATE43_ENTITY_MANAGER_INTERFACE = "org.hibernate.jpa.HibernateEntityManager";
121+
}
122+
123+
private List<String> entityManagerClassNames;
101124

102125
/**
103126
* Creates a new {@link PersistenceProvider}.
104127
*
105-
* @param entityManagerClassName the name of the provider specific {@link EntityManager} implementation
128+
* @param entityManagerClassNames the names of the provider specific {@link EntityManager} implementations. Must not
129+
* be {@literal null} or empty.
106130
*/
107-
private PersistenceProvider(String entityManagerClassName) {
131+
private PersistenceProvider(String... entityManagerClassNames) {
108132

109-
this.entityManagerClassName = entityManagerClassName;
133+
Assert.notEmpty(entityManagerClassNames, "EntityManagerClassNames must not be empty!");
134+
135+
this.entityManagerClassNames = Arrays.asList(entityManagerClassNames);
110136
}
111137

112138
/**
@@ -121,8 +147,11 @@ public static PersistenceProvider fromEntityManager(EntityManager em) {
121147
Assert.notNull(em);
122148

123149
for (PersistenceProvider provider : values()) {
124-
if (isEntityManagerOfType(em, provider.entityManagerClassName)) {
125-
return provider;
150+
for (String entityManagerClassName : provider.entityManagerClassNames) {
151+
152+
if (isEntityManagerOfType(em, entityManagerClassName)) {
153+
return provider;
154+
}
126155
}
127156
}
128157

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright 2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.support;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import javax.persistence.EntityManager;
25+
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
import org.mockito.Mockito;
29+
import org.springframework.asm.ClassWriter;
30+
import org.springframework.asm.Opcodes;
31+
import org.springframework.instrument.classloading.ShadowingClassLoader;
32+
import org.springframework.util.ClassUtils;
33+
34+
/**
35+
* Tests for PersistenceProvider detection logic in {@link PersistenceProvider}.
36+
*
37+
* @author Thomas Darimont
38+
*/
39+
public class PersistenceProviderTests {
40+
41+
private ShadowingClassLoader shadowingClassLoader;
42+
43+
@Before
44+
public void setup() {
45+
shadowingClassLoader = new ShadowingClassLoader(getClass().getClassLoader());
46+
}
47+
48+
/**
49+
* @see DATAJPA-444
50+
*/
51+
@Test
52+
public void detectsHibernatePersistenceProviderForHibernateVersionLessThan4dot3() throws Exception {
53+
54+
shadowingClassLoader.excludePackage("org.hibernate");
55+
56+
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE);
57+
58+
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.HIBERNATE));
59+
}
60+
61+
/**
62+
* @see DATAJPA-444
63+
*/
64+
@Test
65+
public void detectsHibernatePersistenceProviderForHibernateVersionGreaterEqual4dot3() throws Exception {
66+
67+
shadowingClassLoader.excludePackage("org.hibernate");
68+
69+
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE);
70+
71+
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.HIBERNATE));
72+
}
73+
74+
@Test
75+
public void detectsOpenJpaPersistenceProvider() throws Exception {
76+
77+
shadowingClassLoader.excludePackage("org.apache.openjpa.persistence");
78+
79+
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.OPENJPA_ENTITY_MANAGER_INTERFACE);
80+
81+
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.OPEN_JPA));
82+
}
83+
84+
@Test
85+
public void detectsEclipseLinkPersistenceProvider() throws Exception {
86+
87+
shadowingClassLoader.excludePackage("org.eclipse.persistence.jpa");
88+
89+
EntityManager em = mockProviderSpecificEntityManagerInterface(PersistenceProvider.Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE);
90+
91+
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.ECLIPSELINK));
92+
}
93+
94+
@Test
95+
public void fallbackToGenericJpaForUnknownPersistenceProvider() throws Exception {
96+
97+
EntityManager em = mockProviderSpecificEntityManagerInterface("foo.bar.unknown.jpa.JpaEntityManager");
98+
99+
assertThat(PersistenceProvider.fromEntityManager(em), is(PersistenceProvider.GENERIC_JPA));
100+
}
101+
102+
private EntityManager mockProviderSpecificEntityManagerInterface(String interfaceName) throws ClassNotFoundException {
103+
104+
Class<?> providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader,
105+
EntityManager.class);
106+
107+
EntityManager em = EntityManager.class.cast(Mockito.mock(providerSpecificEntityManagerInterface));
108+
Mockito.when(em.getDelegate()).thenReturn(em); // delegate is used to determine the classloader of the provider
109+
// specific interface, therefore we return the proxied
110+
// EntityManager.
111+
112+
return em;
113+
}
114+
115+
static class InterfaceGenerator implements Opcodes {
116+
117+
public static Class<?> generate(final String interfaceName, ClassLoader parentClassLoader,
118+
final Class<?>... interfaces) throws ClassNotFoundException {
119+
120+
class CustomClassLoader extends ClassLoader {
121+
122+
public CustomClassLoader(ClassLoader parent) {
123+
super(parent);
124+
}
125+
126+
@Override
127+
protected Class<?> findClass(String name) throws ClassNotFoundException {
128+
129+
if (name.equals(interfaceName)) {
130+
131+
byte[] byteCode = generateByteCodeForInterface(interfaceName, interfaces);
132+
return defineClass(name, byteCode, 0, byteCode.length);
133+
}
134+
135+
return super.findClass(name);
136+
}
137+
}
138+
139+
return new CustomClassLoader(parentClassLoader).loadClass(interfaceName);
140+
}
141+
142+
private static byte[] generateByteCodeForInterface(final String interfaceName, Class<?>... interfacesToImplement) {
143+
144+
String interfaceResourcePath = ClassUtils.convertClassNameToResourcePath(interfaceName);
145+
ClassWriter cw = new ClassWriter(false);
146+
cw.visit(V1_6, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, interfaceResourcePath, null, "java/lang/Object",
147+
toResourcePaths(interfacesToImplement));
148+
cw.visitSource(interfaceResourcePath + ".java", null);
149+
cw.visitEnd();
150+
151+
return cw.toByteArray();
152+
}
153+
154+
private static String[] toResourcePaths(Class<?>... interfacesToImplement) {
155+
156+
List<String> interfaceResourcePaths = new ArrayList<String>(interfacesToImplement.length);
157+
for (Class<?> iface : interfacesToImplement) {
158+
interfaceResourcePaths.add(ClassUtils.convertClassNameToResourcePath(iface.getName()));
159+
}
160+
161+
return interfaceResourcePaths.toArray(new String[interfaceResourcePaths.size()]);
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)