DATAJPA-1075 - Append ad hoc EntityGraph subgraphs properly.

We now make sure to append instead of recreate subgraphs correctly when AttributeNodes already exist for a given property or dot path. This change fixes errors when creating ad hoc fetch graphs like below where multiple properties are loaded via a subgraph on the same type.

@EntityGraph(attributePaths = { "colleagues.roles", "colleagues.colleagues" })

Previously the first path was accidentally dropped. Additionally we fixed issues with the creation of "deep" ad hoc fetch graphs via @EntityGraph so that


@EntityGraph(attributePaths = { "roles", "colleagues.roles", "colleagues.colleagues.roles" })

can be used as a short form of

@NamedEntityGraph(name = "User.deepGraph",
        attributeNodes = {
                @NamedAttributeNode("roles"),
                @NamedAttributeNode(value="colleagues", subgraph = "User.colleagues")
        },
        subgraphs = {
                @NamedSubgraph(name = "User.colleagues", attributeNodes = {
                        @NamedAttributeNode("roles"),
                        @NamedAttributeNode(value = "colleagues", subgraph = "User.colleaguesOfColleagues")
                }),
                @NamedSubgraph(name="User.colleaguesOfColleagues", attributeNodes = {
                        @NamedAttributeNode("roles"),
                })

        })
})

We had to disable EclipseLink tests for this one since there seems to be a glitch when calling EntityManager.clear() prior to applying the fetch-/loadgraph causing the properties not to be in the correct load state. Omitting EntityManager.clear() is not an option since the load state is always LOADED even when leaving out query hints.

Related issue: DATAJPA-1041.
Original pull request: #192.
Related pull request: #188.
This commit is contained in:
Christoph Strobl
2017-03-13 09:55:31 +01:00
committed by Oliver Gierke
parent 998a63a4bb
commit 86a559cac6
9 changed files with 560 additions and 50 deletions

View File

@@ -149,81 +149,103 @@ public class Jpa21Utils {
// Sort to ensure that the intermediate entity subgraphs are created accordingly.
Collections.sort(attributePaths);
Collections.reverse(attributePaths);
// We build the entity graph based on the paths with highest depth first
for (String path : attributePaths) {
// Fast path - just single attribute
if (!path.contains(".")) {
if (findAttributeNode(path, entityGraph) == null) {
entityGraph.addAttributeNodes(path);
}
continue;
}
// We need to build nested sub fetch graphs
String[] pathComponents = StringUtils.delimitedListToStringArray(path, ".");
Subgraph<?> parent = null;
createGraph(pathComponents, 0, entityGraph, null);
}
}
for (int c = 0; c < pathComponents.length - 1; c++) {
parent = c == 0 ? findOrCreateSubgraph(pathComponents[c], entityGraph) : parent.addSubgraph(pathComponents[c]);
private static void createGraph(String[] pathComponents, int offset, EntityGraph<?> root, Subgraph<?> parent) {
String attributeName = pathComponents[offset];
// we found our leaf property, now let's see if it already exists and add it if not
if (pathComponents.length - 1 == offset) {
if (parent == null && !exists(attributeName, root.getAttributeNodes())) {
root.addAttributeNodes(attributeName);
} else if (parent != null && !exists(attributeName, parent.getAttributeNodes())) {
parent.addAttributeNodes(attributeName);
}
return;
}
parent.addAttributeNodes(pathComponents[pathComponents.length - 1]);
AttributeNode<?> node = findAttributeNode(attributeName, root, parent);
if (node != null) {
Subgraph<?> subgraph = getSubgraph(node);
if (subgraph == null) {
subgraph = parent != null ? parent.addSubgraph(attributeName) : root.addSubgraph(attributeName);
}
createGraph(pathComponents, offset + 1, root, subgraph);
return;
}
if (parent == null) {
createGraph(pathComponents, offset + 1, root, root.addSubgraph(attributeName));
} else {
createGraph(pathComponents, offset + 1, root, parent.addSubgraph(attributeName));
}
}
/**
* Returns the {@link Subgraph} with the given name fro the given {@link EntityGraph} or creates a new one if none
* already available.
*
* @param name
* @param entityGraph
* Checks the given {@link List} of {@link AttributeNode}s for the existence of an {@link AttributeNode} matching the
* given {@literal attributeNodeName}.
*
* @param attributeNodeName
* @param nodes
* @return
*/
private static Subgraph<?> findOrCreateSubgraph(String name, EntityGraph<?> entityGraph) {
Subgraph<?> subgraph = findSubgraph(name, entityGraph);
return subgraph != null ? subgraph : entityGraph.addSubgraph(name);
private static boolean exists(String attributeNodeName, List<AttributeNode<?>> nodes) {
return findAttributeNode(attributeNodeName, nodes) != null;
}
/**
* Returns the {@link Subgraph} with the given name from the given {@link EntityGraph}.
*
* @param name
* Find the {@link AttributeNode} matching the given {@literal attributeNodeName} in given {@link Subgraph} or
* {@link EntityGraph} favoring matches {@link Subgraph} over {@link EntityGraph}.
*
* @param attributeNodeName
* @param entityGraph
* @return
* @param parent
* @return {@literal null} if not found.
*/
private static Subgraph<?> findSubgraph(String name, EntityGraph<?> entityGraph) {
AttributeNode<?> node = findAttributeNode(name, entityGraph);
if (node != null && !ObjectUtils.isEmpty(node.getSubgraphs())) {
return node.getSubgraphs().values().iterator().next();
}
return null;
private static AttributeNode<?> findAttributeNode(String attributeNodeName, EntityGraph<?> entityGraph,
Subgraph parent) {
return findAttributeNode(attributeNodeName,
parent != null ? parent.getAttributeNodes() : entityGraph.getAttributeNodes());
}
/**
* Returns the {@link AttributeNode} with the given name if present in the given {@link EntityGraph}.
*
* @param name
* @param entityGraph must not be {@literal null}.
* @return
* Find the {@link AttributeNode} matching the given {@literal attributeNodeName} in given {@link List} of
* {@link AttributeNode}s.
*
* @param attributeNodeName
* @param nodes
* @return {@literal null} if not found.
*/
private static AttributeNode<?> findAttributeNode(String name, EntityGraph<?> entityGraph) {
private static AttributeNode<?> findAttributeNode(String attributeNodeName, List<AttributeNode<?>> nodes) {
for (AttributeNode<?> node : entityGraph.getAttributeNodes()) {
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), name)) {
for (AttributeNode<?> node : nodes) {
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), attributeNodeName)) {
return node;
}
}
return null;
}
/**
* Extracts the first {@link Subgraph} from the given {@link AttributeNode}. Ignores any potential different
* {@link Subgraph}s registered for more concrete {@link Class}es as the dynamically created graph does not
* distinguish between those.
*
* @param node
* @return
*/
private static Subgraph<?> getSubgraph(AttributeNode<?> node) {
return node.getSubgraphs().isEmpty() ? null : node.getSubgraphs().values().iterator().next();
}
}

View File

@@ -64,7 +64,17 @@ import javax.persistence.TemporalType;
attributeNodes = { @NamedAttributeNode("roles"),
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
subgraphs = { @NamedSubgraph(name = "User.colleagues",
attributeNodes = { @NamedAttributeNode("colleagues"), @NamedAttributeNode("roles") }) }) })
attributeNodes = { @NamedAttributeNode("colleagues"),
@NamedAttributeNode("roles") }) }),
@NamedEntityGraph(name = "User.deepGraph",
attributeNodes = { @NamedAttributeNode("roles"),
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
subgraphs = {
@NamedSubgraph(name = "User.colleagues",
attributeNodes = { @NamedAttributeNode("roles"),
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleaguesOfColleagues") }),
@NamedSubgraph(name = "User.colleaguesOfColleagues",
attributeNodes = { @NamedAttributeNode("roles"), }) }) })
@NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1")
@NamedStoredProcedureQueries({ //
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2017 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.
@@ -21,6 +21,7 @@ import org.springframework.test.context.ContextConfiguration;
/**
* @author Oliver Gierke
* @author Christoph Strobl
*/
@ContextConfiguration("classpath:eclipselink.xml")
public class EclipseLinkEntityGraphRepositoryMethodsIntegrationTests
@@ -37,4 +38,16 @@ public class EclipseLinkEntityGraphRepositoryMethodsIntegrationTests
@Ignore
@Test
public void shouldRespectDynamicFetchGraphForGetOneWithAttributeNamesById() {}
@Ignore
@Test
public void shouldCreateDynamicGraphWithMultipleLevelsOfSubgraphs() {}
@Ignore
@Test
public void shouldRespectConfiguredJpaEntityGraphInFindOne() {}
@Ignore
@Test
public void shouldRespectInferFetchGraphFromMethodName() {}
}

View File

@@ -78,6 +78,7 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
ollie = repository.save(ollie);
tom.getColleagues().add(ollie);
christoph.addRole(role);
christoph = repository.save(christoph);
ollie.getColleagues().add(christoph);
@@ -88,6 +89,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
public void shouldRespectConfiguredJpaEntityGraph() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
List<User> result = repository.findAll();
@@ -100,6 +103,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
public void shouldRespectConfiguredJpaEntityGraphInFindOne() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
User user = repository.findOne(tom.getId());
@@ -112,6 +117,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
public void shouldRespectInferFetchGraphFromMethodName() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
User user = repository.getOneWithDefinedEntityGraphById(tom.getId());
@@ -124,6 +131,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
public void shouldRespectDynamicFetchGraphForGetOneWithAttributeNamesById() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
em.flush();
em.clear();
@@ -145,6 +154,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
public void shouldRespectConfiguredJpaEntityGraphWithPaginationAndQueryDslPredicates() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
Page<User> page = repository.findAll(QUser.user.firstname.isNotNull(), new PageRequest(0, 100));
List<User> result = page.getContent();
@@ -195,4 +206,28 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
assertThat(util.isLoaded(colleague, "roles"), is(true));
}
}
@Test // DATAJPA-1041, DATAJPA-1075
public void shouldCreateDynamicGraphWithMultipleLevelsOfSubgraphs() {
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
em.flush();
em.clear();
User user = repository.findOneWithDeepGraphById(tom.getId());
assertThat(user, is(notNullValue()));
assertThat("colleagues on root should have been fetched by dynamic subgraph declaration",
Persistence.getPersistenceUtil().isLoaded(user, "colleagues"), is(true));
for (User colleague : user.getColleagues()) {
assertThat(Persistence.getPersistenceUtil().isLoaded(colleague, "colleagues"), is(true));
assertThat(Persistence.getPersistenceUtil().isLoaded(colleague, "roles"), is(true));
for (User colleagueOfColleague : colleague.getColleagues()) {
assertThat(Persistence.getPersistenceUtil().isLoaded(colleagueOfColleague, "roles"), is(true));
assertThat(Persistence.getPersistenceUtil().isLoaded(colleagueOfColleague, "colleagues"), is(false));
}
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2017 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.query;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Christoph Strobl
*/
@ContextConfiguration("classpath:eclipselink.xml")
public class EclipseLinkJpa21UtilsTests extends Jpa21UtilsTests {
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2017 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.query;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.springframework.data.jpa.support.EntityManagerTestUtils.*;
import static org.springframework.data.jpa.util.IsAttributeNode.*;
import javax.persistence.AttributeNode;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
@Transactional
public class Jpa21UtilsTests {
@Autowired EntityManager em;
@Test // DATAJPA-1041, DATAJPA-1075
public void shouldCreateGraphWithoutSubGraphCorrectly() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
EntityGraph<User> graph = em.createEntityGraph(User.class);
Jpa21Utils.configureFetchGraphFrom(
new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "roles", "colleagues" }), graph);
AttributeNode<?> roles = findNode("roles", graph);
assertThat(roles, terminatesGraph());
AttributeNode<?> colleagues = findNode("colleagues", graph);
assertThat(colleagues, terminatesGraph());
}
@Test // DATAJPA-1041, DATAJPA-1075
public void shouldCreateGraphWithMultipleSubGraphCorrectly() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
EntityGraph<User> graph = em.createEntityGraph(User.class);
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH,
new String[] { "roles", "colleagues.roles", "colleagues.colleagues" }), graph);
AttributeNode<?> roles = findNode("roles", graph);
assertThat(roles, terminatesGraph());
AttributeNode<?> colleagues = findNode("colleagues", graph);
assertThat(colleagues, terminatesGraphWith("roles", "colleagues"));
}
@Test // DATAJPA-1041, DATAJPA-1075
public void shouldCreateGraphWithDeepSubGraphCorrectly() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
EntityGraph<User> graph = em.createEntityGraph(User.class);
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH,
new String[] { "roles", "colleagues.roles", "colleagues.colleagues.roles" }), graph);
AttributeNode<?> roles = findNode("roles", graph);
assertThat(roles, terminatesGraph());
AttributeNode<?> colleagues = findNode("colleagues", graph);
assertThat(colleagues, terminatesGraphWith("roles"));
assertThat(colleagues, hasSubgraphs("colleagues"));
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
}
@Test // DATAJPA-1041, DATAJPA-1075
public void shouldIgnoreIntermedeateSubGraphNodesThatAreNotNeeded() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
EntityGraph<User> graph = em.createEntityGraph(User.class);
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "roles",
"colleagues", "colleagues.roles", "colleagues.colleagues", "colleagues.colleagues.roles" }), graph);
AttributeNode<?> roles = findNode("roles", graph);
assertThat(roles, terminatesGraph());
AttributeNode<?> colleagues = findNode("colleagues", graph);
assertThat(colleagues, terminatesGraphWith("roles"));
assertThat(colleagues, hasSubgraphs("colleagues"));
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
}
@Test // DATAJPA-1041, DATAJPA-1075
public void orderOfSubGraphsShouldNotMatter() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
EntityGraph<User> graph = em.createEntityGraph(User.class);
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] {
"colleagues.colleagues.roles", "roles", "colleagues.colleagues", "colleagues", "colleagues.roles" }), graph);
AttributeNode<?> roles = findNode("roles", graph);
assertThat(roles, terminatesGraph());
AttributeNode<?> colleagues = findNode("colleagues", graph);
assertThat(colleagues, terminatesGraphWith("roles"));
assertThat(colleagues, hasSubgraphs("colleagues"));
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
}
@Test(expected = Exception.class) // DATAJPA-1041, DATAJPA-1075
public void errorsOnUnknownProperties() {
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "¯\\_(ツ)_/¯" }),
em.createEntityGraph(User.class));
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2017 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.query;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Christoph Strobl
*/
@ContextConfiguration("classpath:openjpa.xml")
public class OpenJpaJpa21UtilsTests extends Jpa21UtilsTests {
}

View File

@@ -69,4 +69,8 @@ public interface RepositoryMethodsWithEntityGraphConfigRepository
// DATAJPA-1041
@EntityGraph(attributePaths = { "colleagues", "colleagues.roles", "colleagues.colleagues" })
User findOneWithMultipleSubGraphsById(Integer id);
// DATAJPA-1041, DATAJPA-1075
@EntityGraph(attributePaths = { "colleagues", "colleagues.roles", "colleagues.colleagues.roles" })
User findOneWithDeepGraphById(Integer id);
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright 2017 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.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.persistence.AttributeNode;
import javax.persistence.EntityGraph;
import javax.persistence.Subgraph;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
*/
public class IsAttributeNode<T> extends TypeSafeMatcher<AttributeNode<T>> {
private boolean terminatingNodeCheck = false;
private List<String> nodes;
private List<String> subgraphs;
private List<String> errors = new ArrayList<String>();
@Override
protected boolean matchesSafely(AttributeNode<T> item) {
if (item == null) {
errors.add("AttributeNode was null!");
return false;
}
if (terminatingNodeCheck) {
if (!CollectionUtils.isEmpty(item.getSubgraphs())) {
errors.add(String.format("'%s' was expected to be a terminating node but has subgraphs %s.",
item.getAttributeName(), extractExistingAttributeNames(item.getSubgraphs().values().iterator().next())));
return false;
}
return true;
}
if (CollectionUtils.isEmpty(item.getSubgraphs())) {
if (!CollectionUtils.isEmpty(nodes)) {
errors
.add(String.format("Leaf properties %s could not be found. The node does not have any subgraphs.", nodes));
}
if (!CollectionUtils.isEmpty(subgraphs)) {
errors.add(String.format("Subgraphs %s could not be found. The node does not have any subgraphs.", subgraphs));
}
return false;
}
Subgraph<?> graph = item.getSubgraphs().values().iterator().next();
if (!CollectionUtils.isEmpty(nodes)) {
for (String nodeName : nodes) {
AttributeNode<?> node = findNode(nodeName, graph.getAttributeNodes());
if (node == null) {
errors.add(String.format("AttributeNode '%s' could not be found in subgraph for '%s'. Know nodes are: %s.",
nodeName, item.getAttributeName(), extractExistingAttributeNames(graph)));
return false;
}
if (!CollectionUtils.isEmpty(node.getSubgraphs())) {
errors.add(String.format("AttributeNode %s of subgraph %s is not a leaf property but has % SubGraph(s).",
nodeName, item.getAttributeName(), node.getSubgraphs().size()));
return false;
}
}
}
if (!CollectionUtils.isEmpty(subgraphs)) {
for (String subgraphName : subgraphs) {
AttributeNode<?> node = findNode(subgraphName, graph.getAttributeNodes());
if (node == null) {
errors.add(String.format("Subgraph '%s' could not be found in SubGraph for '%s'. Know nodes are: %s.",
subgraphName, item.getAttributeName(), extractExistingAttributeNames(graph)));
return false;
}
if (CollectionUtils.isEmpty(node.getSubgraphs())) {
errors.add(String.format("'%s' of SubGraph '%s' is not a SubGraph.", subgraphName, item.getAttributeName()));
return false;
}
}
}
return true;
}
@Override
public void describeTo(Description description) {
for (String error : errors) {
description.appendText(error);
}
}
/**
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the root of the given {@literal graph}.
*
* @param nodeName
* @param graph
* @return
*/
public static AttributeNode<?> findNode(String nodeName, EntityGraph<?> graph) {
if (graph == null) {
return null;
}
return findNode(nodeName, graph.getAttributeNodes());
}
/**
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the {@link List} of given {@literal nodes}.
*
* @param nodeName
* @param nodes
* @return
*/
public static AttributeNode<?> findNode(String nodeName, List<AttributeNode<?>> nodes) {
if (CollectionUtils.isEmpty(nodes)) {
return null;
}
for (AttributeNode<?> node : nodes) {
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), nodeName)) {
return node;
}
}
return null;
}
/**
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the first {@link Subgraph} of the given
* {@literal node}.
*
* @param attributeName
* @param node
* @return
*/
public static AttributeNode<?> findNode(String attributeName, AttributeNode<?> node) {
if (CollectionUtils.isEmpty(node.getSubgraphs())) {
return null;
}
Subgraph<?> subgraph = node.getSubgraphs().values().iterator().next();
return findNode(attributeName, subgraph.getAttributeNodes());
}
private List<String> extractExistingAttributeNames(Subgraph<?> graph) {
List<String> result = new ArrayList<String>(graph.getAttributeNodes().size());
for (AttributeNode<?> node : graph.getAttributeNodes()) {
result.add(node.getAttributeName());
}
return result;
}
/**
* Asserts that the fetch graph terminates with {@link AttributeNode}s having the given {@literal nodeNames}.
*
* @param nodeNames
* @return
*/
public static IsAttributeNode terminatesGraphWith(String... nodeNames) {
IsAttributeNode matcher = new IsAttributeNode();
matcher.nodes = Arrays.asList(nodeNames);
return matcher;
}
/**
* Asserts that the fetch graph continues with {@link AttributeNode}s having {@link AttributeNode#getSubgraphs()} with
* given {@literal subgraphNames}.
*
* @return
*/
public static IsAttributeNode hasSubgraphs(String... subgraphNames) {
IsAttributeNode matcher = new IsAttributeNode();
matcher.subgraphs = Arrays.asList(subgraphNames);
return matcher;
}
/**
* Asserts that the fetch graph terminates with the given {@link AttributeNode} by checking
* {@link AttributeNode#getSubgraphs()} is empty.
*
* @return
*/
public static IsAttributeNode terminatesGraph() {
IsAttributeNode matcher = new IsAttributeNode();
matcher.terminatingNodeCheck = true;
return matcher;
}
}