You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by rz...@apache.org on 2026/08/20 10:46:51 UTC
(openjpa) 03/03: [OPENJPA-2985] Implement EntityManager.find(EntityGraph, Object, FindOption...)
This is an automated email from the ASF dual-hosted git repository.
rzo1 pushed a commit to branch OPENJPA-2985
in repository https://gitbox.apache.org/repos/asf/openjpa.git
commit c52b5f053c35fc3e8876f16f5dd8def7b8bd1d4d
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Thu Aug 20 12:38:29 2026 +0200
[OPENJPA-2985] Implement EntityManager.find(EntityGraph, Object, FindOption...)
find(EntityGraph, Object, FindOption...) threw UnsupportedOperationException.
It now looks up an instance of the root entity type of the given graph
(EntityGraphImpl.getEntityType()) by primary key and applies the graph as a
JPA 3.2 load graph: every attribute named by the graph and, recursively, by
its subgraphs and key subgraphs is added to the fetch plan, while attributes
outside the graph keep their declared fetch behaviour - the default fetch
group is deliberately left in place, which is what distinguishes a load graph
from a fetch graph.
Details:
- The graph is applied to a fetch plan pushed for the duration of the call
only, so neither the entity manager's fetch plan nor its maximum fetch
depth is affected afterwards. The maximum fetch depth is raised only when
the graph is nested deeper than a currently configured finite depth; an
infinite depth (-1) is never lowered.
- Attributes are resolved through ClassMetaData/FieldMetaData and added by
their full name (declaring type + field), so attributes inherited from a
mapped superclass or a superclass entity are matched correctly.
- A subgraph's declared class type is preferred, falling back to the declared
type of the owning value's element/key, because for a plural attribute
EntityGraphImpl derives the subgraph type from Attribute.getJavaType(),
which is the collection type.
- Recursion is bounded by a path scoped identity guard, so a graph that
references itself terminates.
- A null graph, a null primary key, a non-entity root type and a foreign
EntityGraph implementation all raise IllegalArgumentException; the message
for a foreign implementation mirrors
EntityManagerFactoryImpl.addNamedEntityGraph.
- The FindOption parsing of find(Class, Object, FindOption...) was extracted
into a shared private helper so both overloads behave identically. The only
observable delta on the existing overload is that an explicit null option
array is now a no-op instead of an NPE, matching lock(Object, LockModeType,
LockOption...).
Tested by the new TestEntityGraphFind (10 tests) in the existing entitygraph
test package; EGDepartment gained a lazy inverse collection so a graph has
something to change. The tests assert the load state via the state manager's
loaded bit set and pair every positive assertion with a fresh entity manager
control that asserts the attribute is NOT loaded without the graph, covering
a flat graph, a subgraph, a cyclic graph, find options, a missing row and the
three IllegalArgumentException cases.
---
.../persistence/entitygraph/EGDepartment.java | 10 +
.../entitygraph/TestEntityGraphFind.java | 253 +++++++++++++++++++++
.../openjpa/persistence/EntityManagerImpl.java | 177 ++++++++++++--
3 files changed, 426 insertions(+), 14 deletions(-)
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/EGDepartment.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/EGDepartment.java
index 83b4b12b6..054b21682 100644
--- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/EGDepartment.java
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/EGDepartment.java
@@ -18,8 +18,13 @@
*/
package org.apache.openjpa.persistence.entitygraph;
+import java.util.ArrayList;
+import java.util.List;
+
import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
@Entity
@@ -31,6 +36,9 @@ public class EGDepartment {
private String name;
+ @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
+ private List<EGEmployee> employees = new ArrayList<>();
+
public EGDepartment() {}
public EGDepartment(int id, String name) {
@@ -42,4 +50,6 @@ public class EGDepartment {
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
+ public List<EGEmployee> getEmployees() { return employees; }
+ public void setEmployees(List<EGEmployee> employees) { this.employees = employees; }
}
diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/TestEntityGraphFind.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/TestEntityGraphFind.java
new file mode 100644
index 000000000..7b3393c0a
--- /dev/null
+++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/entitygraph/TestEntityGraphFind.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.openjpa.persistence.entitygraph;
+
+import java.lang.reflect.Proxy;
+
+import jakarta.persistence.CacheRetrieveMode;
+import jakarta.persistence.CacheStoreMode;
+import jakarta.persistence.EntityGraph;
+import jakarta.persistence.EntityManager;
+
+import org.apache.openjpa.enhance.PersistenceCapable;
+import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+import org.apache.openjpa.util.ImplHelper;
+
+/**
+ * Tests {@code EntityManager.find(EntityGraph, Object, FindOption...)} (JPA 3.2).
+ * The graph is interpreted as a <em>load graph</em>: attributes named by the graph are fetched eagerly,
+ * attributes outside the graph keep their declared fetch behaviour.
+ */
+public class TestEntityGraphFind extends SingleEMFTestCase {
+
+ private static final int DEPT_ID = 1;
+ private static final int EMP_ID = 10;
+
+ @Override
+ public void setUp() {
+ setUp(EGEmployee.class, EGEmployee2.class, EGEmployee3.class,
+ EGDepartment.class, CLEAR_TABLES);
+ createData();
+ }
+
+ private void createData() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ em.getTransaction().begin();
+ EGDepartment dept = new EGDepartment(DEPT_ID, "Engineering");
+ em.persist(dept);
+ for (int i = 0; i < 2; i++) {
+ EGEmployee emp = new EGEmployee();
+ emp.setId(EMP_ID + i);
+ emp.setFirstName("First" + i);
+ emp.setLastName("Last" + i);
+ emp.setSalary(1000 + i);
+ emp.setDepartment(dept);
+ dept.getEmployees().add(emp);
+ em.persist(emp);
+ }
+ em.getTransaction().commit();
+ } finally {
+ if (em.getTransaction().isActive()) {
+ em.getTransaction().rollback();
+ }
+ em.close();
+ }
+ }
+
+ /**
+ * Direct, non-brittle load state check: looks at the state manager's loaded bit for the given attribute
+ * rather than counting SQL statements.
+ */
+ private boolean isLoaded(Object entity, String attribute) {
+ PersistenceCapable pc = ImplHelper.toPersistenceCapable(entity, emf.getConfiguration());
+ assertNotNull("not a persistence capable instance", pc);
+ OpenJPAStateManager sm = (OpenJPAStateManager) pc.pcGetStateManager();
+ assertNotNull("entity is not managed", sm);
+ FieldMetaData fmd = sm.getMetaData().getField(attribute);
+ assertNotNull("no such persistent attribute: " + attribute, fmd);
+ return sm.getLoaded().get(fmd.getIndex());
+ }
+
+ /**
+ * Baseline: without a graph the lazy collection is not loaded. Without this control the positive
+ * assertion below would be vacuous.
+ */
+ public void testFindWithoutGraphLeavesLazyAttributeUnloaded() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EGDepartment dept = em.find(EGDepartment.class, DEPT_ID);
+ assertNotNull(dept);
+ assertFalse("employees must be lazy without a graph", isLoaded(dept, "employees"));
+ } finally {
+ em.close();
+ }
+ }
+
+ /**
+ * The load bearing test: the very same lookup with a graph naming the lazy collection loads it eagerly.
+ */
+ public void testFindWithEntityGraphLoadsGraphAttribute() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> graph = em.createEntityGraph(EGDepartment.class);
+ graph.addAttributeNodes("employees");
+ EGDepartment dept = em.find(graph, DEPT_ID);
+ assertNotNull(dept);
+ assertEquals(DEPT_ID, dept.getId());
+ assertTrue("employees must be loaded by the load graph", isLoaded(dept, "employees"));
+ assertEquals(2, dept.getEmployees().size());
+ } finally {
+ em.close();
+ }
+ }
+
+ /**
+ * Load graph, not fetch graph: attributes outside the graph keep their declared (eager) behaviour.
+ */
+ public void testFindWithEntityGraphKeepsNonGraphAttributesDefault() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGEmployee> graph = em.createEntityGraph(EGEmployee.class);
+ graph.addAttributeNodes("firstName");
+ EGEmployee emp = em.find(graph, EMP_ID);
+ assertNotNull(emp);
+ assertEquals("First0", emp.getFirstName());
+ assertTrue("lastName is outside the graph but eager by default", isLoaded(emp, "lastName"));
+ assertNotNull(emp.getDepartment());
+ } finally {
+ em.close();
+ }
+ }
+
+ /**
+ * Subgraphs are applied recursively: the department reached from the employee has its lazy collection
+ * loaded. The control below proves the plain find() does not.
+ */
+ public void testFindWithSubgraph() {
+ EntityManager control = emf.createEntityManager();
+ try {
+ EGEmployee emp = control.find(EGEmployee.class, EMP_ID);
+ assertNotNull(emp);
+ assertFalse(isLoaded(emp.getDepartment(), "employees"));
+ } finally {
+ control.close();
+ }
+
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGEmployee> graph = em.createEntityGraph(EGEmployee.class);
+ graph.addSubgraph("department").addAttributeNodes("employees");
+ EGEmployee emp = em.find(graph, EMP_ID);
+ assertNotNull(emp);
+ assertNotNull(emp.getDepartment());
+ assertTrue("the subgraph must load department.employees", isLoaded(emp.getDepartment(), "employees"));
+ assertEquals(2, emp.getDepartment().getEmployees().size());
+ } finally {
+ em.close();
+ }
+ }
+
+ /**
+ * A graph that references itself must not send the graph traversal into an endless recursion.
+ */
+ public void testFindWithCyclicGraph() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> graph = em.createEntityGraph(EGDepartment.class);
+ jakarta.persistence.Subgraph<EGEmployee> emps = graph.addSubgraph("employees", EGEmployee.class);
+ emps.addSubgraph("department", EGDepartment.class).addAttributeNodes("employees");
+ EGDepartment dept = em.find(graph, DEPT_ID);
+ assertNotNull(dept);
+ assertTrue(isLoaded(dept, "employees"));
+ } finally {
+ em.close();
+ }
+ }
+
+ /**
+ * FindOptions are honoured on the graph based overload as well, sharing the option parsing with
+ * {@code find(Class, Object, FindOption...)}.
+ */
+ public void testFindWithGraphAndFindOptions() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> graph = em.createEntityGraph(EGDepartment.class);
+ graph.addAttributeNodes("employees");
+ EGDepartment dept = em.find(graph, DEPT_ID, CacheStoreMode.BYPASS, CacheRetrieveMode.BYPASS);
+ assertNotNull(dept);
+ assertTrue(isLoaded(dept, "employees"));
+ } finally {
+ em.close();
+ }
+ }
+
+ public void testFindWithGraphReturnsNullWhenMissing() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> graph = em.createEntityGraph(EGDepartment.class);
+ graph.addAttributeNodes("employees");
+ assertNull(em.find(graph, 9999));
+ } finally {
+ em.close();
+ }
+ }
+
+ public void testFindWithNullGraphThrowsIAE() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ em.find((EntityGraph<EGDepartment>) null, DEPT_ID);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ // expected
+ } finally {
+ em.close();
+ }
+ }
+
+ public void testFindWithNullPrimaryKeyThrowsIAE() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> graph = em.createEntityGraph(EGDepartment.class);
+ em.find(graph, null);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ // expected
+ } finally {
+ em.close();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testFindWithForeignEntityGraphThrowsIAE() {
+ EntityManager em = emf.createEntityManager();
+ try {
+ EntityGraph<EGDepartment> foreign = (EntityGraph<EGDepartment>) Proxy.newProxyInstance(
+ getClass().getClassLoader(), new Class<?>[] { EntityGraph.class }, (p, m, a) -> null);
+ em.find(foreign, DEPT_ID);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(String.valueOf(expected.getMessage()).contains("Unknown EntityGraph implementation"));
+ } finally {
+ em.close();
+ }
+ }
+}
diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
index d202c7ac8..a7762b94c 100644
--- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
+++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
@@ -33,6 +33,7 @@ import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
@@ -41,6 +42,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import jakarta.persistence.AttributeNode;
import jakarta.persistence.CacheRetrieveMode;
import jakarta.persistence.CacheStoreMode;
import jakarta.persistence.ConnectionConsumer;
@@ -55,6 +57,7 @@ import jakarta.persistence.PessimisticLockScope;
import jakarta.persistence.Query;
import jakarta.persistence.RefreshOption;
import jakarta.persistence.StoredProcedureQuery;
+import jakarta.persistence.Subgraph;
import jakarta.persistence.Timeout;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
@@ -98,6 +101,7 @@ import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.meta.MultiQueryMetaData;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.meta.SequenceMetaData;
+import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.persistence.meta.MetamodelImpl;
import org.apache.openjpa.persistence.criteria.CriteriaBuilderImpl;
import org.apache.openjpa.persistence.criteria.OpenJPACriteriaBuilder;
@@ -721,22 +725,38 @@ public class EntityManagerImpl
@Override
public <T> T find(Class<T> cls, Object oid, FindOption... options) {
Map<String, Object> props = new HashMap<>();
+ LockModeType mode = parseFindOptions(props, options);
+ return find(cls, oid, mode, props);
+ }
+
+ /**
+ * Translates the given JPA 3.2 {@link FindOption}s into kernel level properties, which are put into the
+ * given (modifiable) property map, and returns the {@link LockModeType} found among the options, if any.
+ * Unknown (custom) options are ignored.
+ *
+ * @param props the map to receive the translated properties, never null
+ * @param options the options to translate, may be null or empty
+ * @return the lock mode given among the options or null if none was given
+ */
+ private LockModeType parseFindOptions(Map<String, Object> props, FindOption... options) {
LockModeType mode = null;
- for (FindOption opt: options) {
- if (opt instanceof LockModeType lmt) {
- mode = lmt;
- } else if (opt instanceof CacheRetrieveMode crm) {
- props.put(JPAProperties.CACHE_RETRIEVE_MODE, crm);
- } else if (opt instanceof CacheStoreMode csm) {
- props.put(JPAProperties.CACHE_STORE_MODE, csm);
- } else if (opt instanceof PessimisticLockScope pls) {
- props.put(JPAProperties.LOCK_SCOPE, pls);
- } else if (opt instanceof Timeout timeout) {
- props.put(JPAProperties.LOCK_TIMEOUT, timeout.milliseconds());
+ if (options != null) {
+ for (FindOption opt: options) {
+ if (opt instanceof LockModeType lmt) {
+ mode = lmt;
+ } else if (opt instanceof CacheRetrieveMode crm) {
+ props.put(JPAProperties.CACHE_RETRIEVE_MODE, crm);
+ } else if (opt instanceof CacheStoreMode csm) {
+ props.put(JPAProperties.CACHE_STORE_MODE, csm);
+ } else if (opt instanceof PessimisticLockScope pls) {
+ props.put(JPAProperties.LOCK_SCOPE, pls);
+ } else if (opt instanceof Timeout timeout) {
+ props.put(JPAProperties.LOCK_TIMEOUT, timeout.milliseconds());
+ }
+ // open to custom options
}
- // open to custom options
}
- return find(cls, oid, mode, props);
+ return mode;
}
@Override
@@ -2640,9 +2660,138 @@ public class EntityManagerImpl
return meta;
}
+ /**
+ * Finds an instance of the root entity type of the given entity graph by primary key, using the graph
+ * as a <em>load graph</em> as mandated by JPA 3.2: every attribute named by the graph (and, recursively,
+ * by its subgraphs) is fetched eagerly, while attributes not mentioned by the graph keep their declared
+ * fetch behaviour. This is in contrast to a fetch graph, which would additionally force every attribute
+ * outside the graph to be lazy; therefore the default fetch group is deliberately left untouched here.
+ * <p>
+ * The graph is applied to a fetch plan that is pushed for the duration of this call only, so neither the
+ * entity manager's fetch plan nor its maximum fetch depth is affected once the call returns.
+ *
+ * @param entityGraph the load graph, its root type determines the type to look up
+ * @param primaryKey the primary key of the instance to find
+ * @param options optional find options, interpreted exactly as by {@link #find(Class, Object, FindOption...)}
+ * @return the found instance or null if no instance with the given primary key exists
+ * @throws IllegalArgumentException if the graph is null, is not an OpenJPA entity graph, its root type is
+ * not an entity type, or the primary key is null or of an invalid type
+ */
@Override
+ @SuppressWarnings("unchecked")
public <T> T find(EntityGraph<T> entityGraph, Object primaryKey, FindOption... options) {
- throw new UnsupportedOperationException("Not yet implemented (JPA 3.2)");
+ assertNotCloseInvoked();
+ if (entityGraph == null) {
+ throw new IllegalArgumentException("entityGraph is null");
+ }
+ if (!(entityGraph instanceof EntityGraphImpl)) {
+ throw new IllegalArgumentException("Unknown EntityGraph implementation: " + entityGraph.getClass());
+ }
+ EntityGraphImpl<T> graph = (EntityGraphImpl<T>) entityGraph;
+ Class<T> cls = graph.getEntityType();
+ Map<String, Object> props = new HashMap<>();
+ LockModeType mode = parseFindOptions(props, options);
+ try {
+ validateFindArguments(cls, primaryKey);
+ configureCurrentCacheModes(pushFetchPlan(), props);
+ try {
+ FetchPlan fetch = getFetchPlan();
+ configureCurrentFetchPlan(fetch, props, mode, true);
+ applyEntityGraph(fetch, graph);
+ Object oid = _broker.newObjectId(cls, primaryKey);
+ return (T) _broker.find(oid, true, this);
+ } finally {
+ popFetchPlan();
+ }
+ } catch (RuntimeException re) {
+ throw translateException(re);
+ }
+ }
+
+ /**
+ * Applies the given entity graph to the given (already pushed) fetch plan as a load graph, and raises the
+ * plan's maximum fetch depth if - and only if - the graph is nested deeper than the currently configured
+ * finite depth. A max fetch depth of {@link FetchPlan#DEPTH_INFINITE} already covers any graph and is never
+ * lowered.
+ */
+ private void applyEntityGraph(FetchPlan fetch, EntityGraphImpl<?> graph) {
+ MetaDataRepository repos = _broker.getConfiguration().getMetaDataRepositoryInstance();
+ ClassMetaData meta = toMetaData(repos, graph.getEntityType());
+ if (meta == null) {
+ return;
+ }
+ int depth = applyGraphNodes(fetch, meta, graph.getAttributeNodes(), repos,
+ Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>()));
+ int max = fetch.getMaxFetchDepth();
+ if (max != FetchPlan.DEPTH_INFINITE && max < depth) {
+ fetch.setMaxFetchDepth(depth);
+ }
+ }
+
+ /**
+ * Adds every attribute named by the given graph nodes to the given fetch plan and recurses into their
+ * subgraphs. Nodes that do not name a persistent attribute of the given type are silently skipped, so a
+ * loosely declared named entity graph cannot turn a find() into a hard failure.
+ *
+ * @return the number of relation levels spanned by the given nodes, 0 if there are none
+ */
+ private int applyGraphNodes(FetchPlan fetch, ClassMetaData meta, List<AttributeNode<?>> nodes,
+ MetaDataRepository repos, Set<Object> path) {
+ if (meta == null || nodes == null || nodes.isEmpty()) {
+ return 0;
+ }
+ int depth = 1;
+ for (AttributeNode<?> node : nodes) {
+ FieldMetaData fmd = meta.getField(node.getAttributeName());
+ if (fmd == null) {
+ continue;
+ }
+ // the full name is declaringType.fieldName, which is what FetchConfiguration matches against;
+ // building the name from the root class instead would silently miss inherited attributes
+ fetch.addField(fmd.getFullName(false));
+ ValueMetaData element = fmd.getElement();
+ ValueMetaData key = fmd.getKey();
+ depth = Math.max(depth, 1 + applySubgraphs(fetch, node.getSubgraphs(),
+ (element == null) ? null : element.getDeclaredType(), repos, path));
+ depth = Math.max(depth, 1 + applySubgraphs(fetch, node.getKeySubgraphs(),
+ (key == null) ? null : key.getDeclaredType(), repos, path));
+ }
+ return depth;
+ }
+
+ /**
+ * Recurses into the given subgraphs. The type declared by a subgraph is preferred, but for a plural
+ * attribute it may be the collection type rather than the element type, in which case the declared type of
+ * the owning value is used instead. Recursion is bounded by a path scoped identity guard, so a graph that
+ * references itself terminates while a subgraph shared by sibling branches is still expanded.
+ *
+ * @return the number of relation levels spanned below the given subgraphs
+ */
+ private int applySubgraphs(FetchPlan fetch, Map<Class, Subgraph> subgraphs, Class<?> fallbackType,
+ MetaDataRepository repos, Set<Object> path) {
+ int depth = 0;
+ if (subgraphs == null || subgraphs.isEmpty()) {
+ return depth;
+ }
+ for (Subgraph<?> sub : subgraphs.values()) {
+ if (sub == null || !path.add(sub)) {
+ continue;
+ }
+ try {
+ ClassMetaData subMeta = toMetaData(repos, sub.getClassType());
+ if (subMeta == null) {
+ subMeta = toMetaData(repos, fallbackType);
+ }
+ depth = Math.max(depth, applyGraphNodes(fetch, subMeta, sub.getAttributeNodes(), repos, path));
+ } finally {
+ path.remove(sub);
+ }
+ }
+ return depth;
+ }
+
+ private ClassMetaData toMetaData(MetaDataRepository repos, Class<?> cls) {
+ return (cls == null) ? null : repos.getMetaData(cls, _broker.getClassLoader(), false);
}
@Override