Skip to content

DATAJPA-444 - Improve detection of PersistenceProvider implementations. #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.5.0.BUILD-SNAPSHOT</version>
<version>1.5.0.DATAJPA-444.SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;

Expand All @@ -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.
* <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) {

Expand All @@ -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) {

Expand All @@ -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) {

Expand All @@ -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) {

Expand All @@ -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<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);
}

/**
Expand All @@ -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;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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()]);
}
}
}