diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index f99ab2423..f23e9e380 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -237,6 +237,7 @@ public class BrokerImpl private int _lifeCallbackMode = 0; private transient boolean _initializeWasInvoked = false; + private LinkedList _fcs; /** * Set the persistence manager's authentication. This is the first @@ -385,6 +386,20 @@ public class BrokerImpl return _fc; } + public FetchConfiguration pushFetchConfiguration() { + if (_fcs == null) + _fcs = new LinkedList(); + _fcs.add(_fc); + _fc = (FetchConfiguration) _fc.clone(); + return _fc; + } + + public void popFetchConfiguration() { + if (_fcs == null || _fcs.isEmpty()) + throw new UserException(_loc.get("fetch-configuration-stack-empty")); + _fc = (FetchConfiguration) _fcs.removeLast(); + } + public int getConnectionRetainMode() { return _connRetainMode; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java index 414267091..8e97520f1 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DelegatingBroker.java @@ -122,6 +122,22 @@ public class DelegatingBroker } } + public FetchConfiguration pushFetchConfiguration() { + try { + return _broker.pushFetchConfiguration(); + } catch (RuntimeException re) { + throw translate(re); + } + } + + public void popFetchConfiguration() { + try { + _broker.popFetchConfiguration(); + } catch (RuntimeException re) { + throw translate(re); + } + } + public ClassLoader getClassLoader() { try { return _broker.getClassLoader(); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java index a05705ebc..1d8d9d500 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StoreContext.java @@ -61,6 +61,27 @@ public interface StoreContext { */ public FetchConfiguration getFetchConfiguration(); + /** + * Pushes a new fetch configuration that inherits from the current + * fetch configuration onto a stack, and makes the new configuration + * the active one. + * + * @since 1.1.0 + * @return the new fetch configuration + */ + public FetchConfiguration pushFetchConfiguration(); + + /** + * Pops the fetch configuration from the top of the stack, making the + * next one down the active one. This returns void to avoid confusion, + * since fetch configurations tend to be used in method-chaining + * patterns often. + * + * @since 1.1.0 + * @throws UserException if the fetch configuration stack is empty + */ + public void popFetchConfiguration(); + /** * Return the current thread's class loader at the time this context * was obtained. diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties index 5c8d73732..c82129688 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/localizer.properties @@ -399,3 +399,4 @@ cant-serialize-pessimistic-broker: Serialization not allowed for brokers with \ cant-serialize-connected-broker: Serialization not allowed for brokers with \ an active connection to the database. no-interface-metadata: No metadata was found for managed interface {0}. +fetch-configuration-stack-empty: Fetch configuration stack is empty. diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/fetchgroups/TestFetchGroupStacks.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/fetchgroups/TestFetchGroupStacks.java new file mode 100644 index 000000000..a029d662f --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/fetchgroups/TestFetchGroupStacks.java @@ -0,0 +1,79 @@ +/* + * 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.fetchgroups; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.apache.openjpa.persistence.test.SingleEMTestCase; +import org.apache.openjpa.persistence.PersistenceException; + +public class TestFetchGroupStacks extends SingleEMTestCase { + + public void setUp() { + setUp(FGManager.class, FGDepartment.class, FGEmployee.class, + FGAddress.class); + } + + public void testFetchGroupStacks() { + assertFetchGroups(); + em.getFetchPlan().addFetchGroup("foo"); + assertFetchGroups("foo"); + + { // add one new fetch group + em.pushFetchPlan().addFetchGroup("bar"); // push 1 + assertFetchGroups("foo", "bar"); + + { // add another one + em.pushFetchPlan().addFetchGroup("baz"); // push 2 + assertFetchGroups("foo", "bar", "baz"); + + { // add a fourth, plus one that's already there + em.pushFetchPlan().addFetchGroups("quux", "foo"); // push 3 + assertFetchGroups("foo", "bar", "baz", "quux"); + em.popFetchPlan(); // pop 3 + } + + // "foo" should still be there, since it was there before pop 3 + assertFetchGroups("foo", "bar", "baz"); + em.popFetchPlan(); // pop 2 + } + + assertFetchGroups("foo", "bar"); + em.popFetchPlan(); // pop 1 + } + assertFetchGroups("foo"); + + try { + em.popFetchPlan(); + fail("should be unbalanced"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("stack")); + } + } + + private void assertFetchGroups(String... fgs) { + Set s = new HashSet(); + if (fgs != null) + s.addAll(Arrays.asList(fgs)); + s.add("default"); + assertEquals(s, em.getFetchPlan().getFetchGroups()); + } +} 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 288f8e7e1..cf591de5a 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 @@ -31,6 +31,8 @@ import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; +import java.util.Map; +import java.util.HashMap; import javax.persistence.EntityManager; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; @@ -51,6 +53,7 @@ import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.QueryFlushModes; import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.Seq; +import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.jpql.JPQLParser; import org.apache.openjpa.lib.util.Closeable; import org.apache.openjpa.lib.util.Localizer; @@ -80,7 +83,8 @@ public class EntityManagerImpl private DelegatingBroker _broker; private EntityManagerFactoryImpl _emf; - private FetchPlan _fetch = null; + private Map _plans = + new HashMap(1); private RuntimeExceptionTranslator ret = PersistenceExceptions.getRollbackTranslator(this); @@ -122,10 +126,34 @@ public class EntityManagerImpl assertNotCloseInvoked(); _broker.lock(); try { - if (_fetch == null) - _fetch = _emf.toFetchPlan(_broker, - _broker.getFetchConfiguration()); - return _fetch; + FetchConfiguration fc = _broker.getFetchConfiguration(); + FetchPlan fp = _plans.get(fc); + if (fp == null) { + fp = _emf.toFetchPlan(_broker, fc); + _plans.put(fc, fp); + } + return fp; + } finally { + _broker.unlock(); + } + } + + public FetchPlan pushFetchPlan() { + assertNotCloseInvoked(); + _broker.lock(); + try { + _broker.pushFetchConfiguration(); + return getFetchPlan(); + } finally { + _broker.unlock(); + } + } + + public void popFetchPlan() { + assertNotCloseInvoked(); + _broker.lock(); + try { + _broker.popFetchConfiguration(); } finally { _broker.unlock(); } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java index 41f248726..9ca766bf4 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/OpenJPAEntityManager.java @@ -56,6 +56,24 @@ public interface OpenJPAEntityManager */ public FetchPlan getFetchPlan(); + /** + * Pushes a new fetch plan that inherits from the current fetch plan onto + * a stack, and makes the new plan the active one. + * + * @since 1.1.0 + * @return the new fetch plan + */ + public FetchPlan pushFetchPlan(); + + /** + * Pops the fetch plan from the top of the stack, making the next one down + * the active one. This returns void to avoid confusion, since fetch plans + * tend to be used in method-chaining patterns often. + * + * @since 1.1.0 + */ + public void popFetchPlan(); + /** * Return the connection retain mode for this entity manager. */