diff --git a/pom.xml b/pom.xml index 36cfec88a2..e2d9798319 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAJPA-444.SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. diff --git a/src/main/java/org/springframework/data/jpa/repository/support/PersistenceProvider.java b/src/main/java/org/springframework/data/jpa/repository/support/PersistenceProvider.java index 89a5596990..5433eb417f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/PersistenceProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/PersistenceProvider.java @@ -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 @@ 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.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. + *

+ * 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 @@ protected String getCountQueryPlaceholder() { /** * 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 String extractQueryString(Query query) { /** * 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 String extractQueryString(Query query) { /** * 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 boolean canExtractQuery() { } }; - 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 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 static PersistenceProvider fromEntityManager(EntityManager em) { 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; + } } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/PersistenceProviderTests.java b/src/test/java/org/springframework/data/jpa/repository/support/PersistenceProviderTests.java new file mode 100644 index 0000000000..594eb2c884 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/PersistenceProviderTests.java @@ -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 interfaceResourcePaths = new ArrayList(interfacesToImplement.length); + for (Class iface : interfacesToImplement) { + interfaceResourcePaths.add(ClassUtils.convertClassNameToResourcePath(iface.getName())); + } + + return interfaceResourcePaths.toArray(new String[interfaceResourcePaths.size()]); + } + } +}