Repository: deltaspike Updated Branches: refs/heads/master 8df71b676 -> 6d75f5ea9
DELTASPIKE-1052 support entity graphs built from attribute paths Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/6d75f5ea Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/6d75f5ea Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/6d75f5ea Branch: refs/heads/master Commit: 6d75f5ea95416183964be57275c340f41ab7f82d Parents: 8df71b6 Author: Harald Wellmann <[email protected]> Authored: Tue Dec 29 20:25:03 2015 +0100 Committer: Harald Wellmann <[email protected]> Committed: Tue Dec 29 20:25:03 2015 +0100 ---------------------------------------------------------------------- .../data/impl/graph/EntityGraphHelper.java | 139 ++++++++++++++++++- .../impl/handler/CdiQueryInvocationContext.java | 10 +- .../deltaspike/data/test/ee7/domain/Flat.java | 21 +++ .../deltaspike/data/test/ee7/domain/House.java | 3 + .../test/ee7/graph/HouseRepositoryTest.java | 53 ++++++- .../data/test/ee7/service/HouseRepository.java | 8 ++ 6 files changed, 225 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/graph/EntityGraphHelper.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/graph/EntityGraphHelper.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/graph/EntityGraphHelper.java index 72bad91..eb3b296 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/graph/EntityGraphHelper.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/graph/EntityGraphHelper.java @@ -20,6 +20,10 @@ package org.apache.deltaspike.data.impl.graph; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import javax.persistence.EntityManager; @@ -29,13 +33,42 @@ public final class EntityGraphHelper { private static final Class<?> ENTITY_GRAPH_CLASS; + private static final Class<?> SUBGRAPH_CLASS; + private static final Method ADD_ATTRIBUTE_NODES; + private static final Method ADD_SUBGRAPH; + private static final Method SUBGRAPH_ADD_ATTRIBUTE_NODES; static { ENTITY_GRAPH_CLASS = ClassUtils.tryToLoadClassForName("javax.persistence.EntityGraph"); + SUBGRAPH_CLASS = ClassUtils.tryToLoadClassForName("javax.persistence.Subgraph"); + if (ENTITY_GRAPH_CLASS == null) + { + ADD_ATTRIBUTE_NODES = null; + ADD_SUBGRAPH = null; + SUBGRAPH_ADD_ATTRIBUTE_NODES = null; + } + else + { + try + { + ADD_ATTRIBUTE_NODES = ENTITY_GRAPH_CLASS.getMethod("addAttributeNodes", + String[].class); + ADD_SUBGRAPH = ENTITY_GRAPH_CLASS.getMethod("addSubgraph", String.class); + SUBGRAPH_ADD_ATTRIBUTE_NODES = SUBGRAPH_CLASS.getMethod("addAttributeNodes", + String[].class); + } + catch (NoSuchMethodException e) + { + throw new EntityGraphException(e.getMessage(), e.getCause()); + } + catch (SecurityException e) + { + throw new EntityGraphException(e.getMessage(), e.getCause()); + } + } } - - + private EntityGraphHelper() { // hidden constructor @@ -72,13 +105,111 @@ public final class EntityGraphHelper } } + public static Object createEntityGraph(EntityManager em, Class<?> entityClass) + { + ensureAvailable(); + try + { + Method method = EntityManager.class.getMethod("createEntityGraph", Class.class); + return method.invoke(em, entityClass); + } + catch (NoSuchMethodException e) + { + throw new EntityGraphException("no method EntityManager.createEntityGraph()", e); + } + catch (SecurityException e) + { + throw new EntityGraphException("no access to method EntityManager.createEntityGraph()", + e); + } + catch (IllegalAccessException e) + { + throw new EntityGraphException("no access to method EntityManager.createEntityGraph()", + e); + } + catch (InvocationTargetException e) + { + throw new EntityGraphException(e.getCause().getMessage(), e.getCause()); + } + } + private static void ensureAvailable() { if (!isAvailable()) { - throw new EntityGraphException( - "Class java.persistence.EntityGraph is not available. " + throw new EntityGraphException("Class java.persistence.EntityGraph is not available. " + "Does your PersistenceProvider support JPA 2.1?"); } } + + public static Object addSubgraph(Object entityGraph, String attributeName) + { + try + { + return ADD_SUBGRAPH.invoke(entityGraph, attributeName); + } + catch (IllegalAccessException e) + { + throw new EntityGraphException("no access to method EntityGraph.addSubgraph()", e); + } + catch (InvocationTargetException e) + { + throw new EntityGraphException(e.getCause().getMessage(), e.getCause()); + } + } + + public static void addAttributeNodes(Object graph, String attributeName) + { + try + { + if (ENTITY_GRAPH_CLASS.isInstance(graph)) + { + ADD_ATTRIBUTE_NODES.invoke(graph, new Object[] { new String[] { attributeName } }); + } + else if (SUBGRAPH_CLASS.isInstance(graph)) + { + SUBGRAPH_ADD_ATTRIBUTE_NODES.invoke(graph, + new Object[] { new String[] { attributeName } }); + } + } + catch (IllegalAccessException e) + { + throw new EntityGraphException("no access to method addAttributeNodes()", e); + } + catch (InvocationTargetException e) + { + throw new EntityGraphException(e.getCause().getMessage(), e.getCause()); + } + } + + public static Object buildEntityGraph(EntityManager em, Class<?> entityClass, + String[] attributePaths) + { + Object graph = createEntityGraph(em, entityClass); + List<String> paths = new ArrayList<String>(Arrays.asList(attributePaths)); + + Collections.sort(paths); + Collections.reverse(paths); + + for (String path : attributePaths) + { + if (path.contains(".")) + { + String[] segments = path.split("\\."); + Object parent = addSubgraph(graph, segments[0]); + + for (int i = 1; i < segments.length - 1; i++) + { + addSubgraph(parent, segments[i]); + } + + addAttributeNodes(parent, segments[segments.length - 1]); + } + else + { + addAttributeNodes(graph, path); + } + } + return graph; + } } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java index f156741..40686f0 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java @@ -323,7 +323,15 @@ public class CdiQueryInvocationContext implements QueryInvocationContext } String graphName = entityGraphAnn.value(); - Object graph = EntityGraphHelper.getEntityGraph(getEntityManager(), graphName); + Object graph; + if (graphName.isEmpty()) + { + graph = EntityGraphHelper.buildEntityGraph(getEntityManager(), entityClass, entityGraphAnn.paths()); + } + else + { + graph = EntityGraphHelper.getEntityGraph(getEntityManager(), graphName); + } query.setHint(entityGraphAnn.type().getHintName(), graph); } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/Flat.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/Flat.java b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/Flat.java index c961067..e021be3 100644 --- a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/Flat.java +++ b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/Flat.java @@ -19,11 +19,16 @@ package org.apache.deltaspike.data.test.ee7.domain; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; import javax.persistence.Table; @Entity @@ -36,6 +41,10 @@ public class Flat implements Serializable @Id @GeneratedValue private Long id; + + @OneToMany(mappedBy = "flat", cascade = CascadeType.ALL) + @OrderColumn + private List<Tenant> tenants = new ArrayList<Tenant>(); @ManyToOne private House house; @@ -71,4 +80,16 @@ public class Flat implements Serializable { this.house = house; } + + + public List<Tenant> getTenants() + { + return tenants; + } + + + public void setTenants(List<Tenant> tenants) + { + this.tenants = tenants; + } } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/House.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/House.java b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/House.java index c8afcfd..a0f7c61 100644 --- a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/House.java +++ b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/domain/House.java @@ -30,6 +30,7 @@ import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; import javax.persistence.NamedEntityGraphs; import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; import javax.persistence.Table; @NamedEntityGraphs({ @@ -50,9 +51,11 @@ public class House implements Serializable private String name; @OneToMany(mappedBy = "house", cascade = CascadeType.ALL) + @OrderColumn private List<Flat> flats = new ArrayList<Flat>(); @OneToMany(mappedBy = "house", cascade = CascadeType.ALL) + @OrderColumn private List<Garage> garages = new ArrayList<Garage>(); public Long getId() http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/graph/HouseRepositoryTest.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/graph/HouseRepositoryTest.java b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/graph/HouseRepositoryTest.java index 8803300..37d619f 100644 --- a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/graph/HouseRepositoryTest.java +++ b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/graph/HouseRepositoryTest.java @@ -102,8 +102,6 @@ public class HouseRepositoryTest { House house = repository.findOptionalByName("Bellevue"); assertNotNull(house); - assertNotNull(house.getId()); - assertEquals("Bellevue", house.getName()); PersistenceUnitUtil puu = entityManager.getEntityManagerFactory().getPersistenceUnitUtil(); @@ -117,8 +115,6 @@ public class HouseRepositoryTest { House house = repository.fetchByName("Bellevue"); assertNotNull(house); - assertNotNull(house.getId()); - assertEquals("Bellevue", house.getName()); assertTrue(puu.isLoaded(house, "flats")); assertFalse(puu.isLoaded(house, "garages")); @@ -139,6 +135,41 @@ public class HouseRepositoryTest } } + @Test + @InSequence(5) + public void should_build_dynamic_graph_from_paths() throws Exception + { + House house = repository.fetchByNameWithDynamicGraph("Bellevue"); + assertNotNull(house); + + assertTrue(puu.isLoaded(house, "flats")); + assertTrue(puu.isLoaded(house, "garages")); + + assertEquals(2, house.getFlats().size()); + assertEquals(2, house.getGarages().size()); + + Flat flat = house.getFlats().get(0); + assertFalse(puu.isLoaded(flat, "tenants")); + } + + @Test + @InSequence(6) + public void should_build_dynamic_graph_from_composite_paths() throws Exception + { + House house = repository.fetchByNameWithFlatTenants("Bellevue"); + assertNotNull(house); + + assertTrue(puu.isLoaded(house, "flats")); + assertTrue(puu.isLoaded(house, "garages")); + + assertEquals(2, house.getFlats().size()); + assertEquals(2, house.getGarages().size()); + + Flat flat = house.getFlats().get(0); + assertTrue(puu.isLoaded(flat, "tenants")); + assertEquals(3, flat.getTenants().size()); + } + @Before public void init() throws Exception { @@ -150,6 +181,20 @@ public class HouseRepositoryTest Flat flat1 = new Flat(); flat1.setName("Flat 1"); flat1.setHouse(house); + + Tenant alice = new Tenant(); + alice.setName("Alice"); + alice.setFlat(flat1); + + Tenant bob = new Tenant(); + bob.setName("Bob"); + bob.setFlat(flat1); + + Tenant charlie = new Tenant(); + charlie.setName("Charlie"); + charlie.setFlat(flat1); + + flat1.setTenants(Arrays.asList(alice, bob, charlie)); Flat flat2 = new Flat(); flat2.setName("Flat 2"); http://git-wip-us.apache.org/repos/asf/deltaspike/blob/6d75f5ea/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/service/HouseRepository.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/service/HouseRepository.java b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/service/HouseRepository.java index f9fa797..e0c6121 100644 --- a/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/service/HouseRepository.java +++ b/deltaspike/modules/data/test-ee7/src/test/java/org/apache/deltaspike/data/test/ee7/service/HouseRepository.java @@ -42,6 +42,14 @@ public interface HouseRepository extends FullEntityRepository<House, Long> @EntityGraph("none") House fetchByNameWithInvalidGraph(String name); + @Query("select h from House h where h.name = ?1") + @EntityGraph(paths = {"flats", "garages"}) + House fetchByNameWithDynamicGraph(String name); + + @Query("select h from House h where h.name = ?1") + @EntityGraph(paths = {"flats.tenants", "garages"}) + House fetchByNameWithFlatTenants(String name); + @Modifying @Query("delete from House") int deleteAll();
