| /* |
| * 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.kernel; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.Query; |
| |
| import org.apache.openjpa.jdbc.conf.JDBCConfiguration; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.OracleDictionary; |
| import org.apache.openjpa.persistence.FetchPlan; |
| import org.apache.openjpa.persistence.OpenJPAPersistence; |
| import org.apache.openjpa.persistence.kernel.common.apps.State; |
| import org.apache.openjpa.persistence.kernel.common.apps.Transition; |
| import org.apache.openjpa.persistence.test.SingleEMFTestCase; |
| |
| /** |
| * Test complex relationship graph fetch. |
| * The graph is represented by State (node) and Transition(edges). |
| * Graph nodes (State) holds pair of list of incoming/outgoing edges (Transition). |
| * The problem report [1] showed that traversal from a root node (incorrectly) terminates |
| * at a depth of 1 irrespective of the value of recursion depth and/or max depth depth. |
| * |
| * The test data model is originally reported in |
| * [1] FetchGroup Recursion Problem |
| * <A HREF="http://n2.nabble.com/Fetchgroups-recursion-problem-tc3874382.html#a3874382">mailing list</A>. |
| * |
| * |
| * @author Pinaki Poddar |
| * |
| */ |
| public class TestIndirectRecursion extends SingleEMFTestCase { |
| // The connection matrix |
| static byte[][] transitions = { |
| // s1 s2 s3 s4 s5 |
| { 0, 1, 0, 0, 0 }, // s1 |
| { 1, 0, 1, 1, 0 }, // s2 |
| { 1, 1, 0, 1, 0 }, // s3 |
| { 0, 0, 0, 0, 1 }, // s4 |
| { 0, 0, 0, 0, 0 } // s5 |
| }; |
| |
| @Override |
| public void setUp() { |
| super.setUp(DROP_TABLES, State.class, Transition.class); |
| |
| DBDictionary dict = ((JDBCConfiguration) emf.getConfiguration()).getDBDictionaryInstance(); |
| if (dict instanceof OracleDictionary) { |
| ((OracleDictionary) dict).useTriggersForAutoAssign = true; |
| } |
| } |
| |
| public void testFetch() { |
| EntityManager em = emf.createEntityManager(); |
| em.getTransaction().begin(); |
| |
| int N = transitions.length; |
| State[] states = new State[N]; |
| // create nodes |
| for (int i = 1; i <= N; i++) { |
| State s = new State(); |
| s.setName("s" + i); |
| em.persist(s); |
| states[i - 1] = s; |
| } |
| // create edges as per the transition matrix |
| for (int i = 0; i < N; i++) { |
| for (int j = 0; j < N; j++) { |
| if (transitions[i][j] == 1) { |
| newTransition(states[i], states[j]); |
| } |
| } |
| } |
| em.getTransaction().commit(); |
| |
| em.clear(); |
| |
| // Select root (state 1) |
| Query query = em.createQuery("select s from State s where s.name=:name"); |
| FetchPlan fetch = OpenJPAPersistence.cast(query).getFetchPlan(); |
| fetch.setMaxFetchDepth(15); |
| fetch.addField(State.class, "incomingTransitions"); |
| fetch.addField(State.class, "outgoingTransitions"); |
| fetch.addField(Transition.class, "toState"); |
| fetch.addField(Transition.class, "fromState"); |
| State qs1 = (State) query.setParameter("name", "s1").getSingleResult(); |
| |
| em.close(); // will not load anything anymore |
| |
| byte[][] actualTransitionMatrix = new byte[5][5]; |
| fillTransitionMatrix(actualTransitionMatrix, new HashSet<>(), qs1); |
| assertMatrixEqual(transitions, actualTransitionMatrix); |
| |
| } |
| |
| void assertMatrixEqual(byte[][] expected, byte[][] actual) { |
| int N = transitions.length; |
| for (int i = 0; i < N; i++) { |
| for (int j = 0; j < N; j++) { |
| assertEquals("Transition(s" + (i+1) + ", s" + (j+1) + ") does not match", expected[i][j], actual[i][j]); |
| } |
| } |
| } |
| |
| void fillTransitionMatrix(byte[][] matrix, Set<State> visited, State s) { |
| if (visited.contains(s)) |
| return; |
| List<Transition> outgoings = s.getOutgoingTransitions(); |
| if (outgoings != null) { |
| for (Transition t : outgoings) { |
| fillTransitionMatrix(matrix, t); |
| } |
| } |
| visited.add(s); |
| |
| if (outgoings != null) { |
| for (Transition t : outgoings) { |
| fillTransitionMatrix(matrix, visited, t.getToState()); |
| } |
| } |
| |
| } |
| |
| void fillTransitionMatrix(byte[][] matrix, Transition t) { |
| matrix[getIndex(t.getFromState())][getIndex(t.getToState())] = 1; |
| } |
| |
| int getIndex(State s) { |
| return Integer.parseInt(s.getName().substring(1)) - 1; |
| } |
| |
| Transition newTransition(State from, State to) { |
| Transition t = new Transition(); |
| t.setFromState(from); |
| t.setToState(to); |
| t.setName(from.getName()+"->"+to.getName()); |
| from.addOutgoingTransitions(t); |
| to.addIncomingTransitions(t); |
| return t; |
| } |
| |
| /** |
| * Find a state of the given name in the list of outgoing transition of the |
| * given State. |
| */ |
| State findOutgoingState(String name, State root) { |
| List<Transition> transitions = root.getOutgoingTransitions(); |
| for (Transition t : transitions) { |
| if (t.getToState().getName().equals(name)) |
| return t.getToState(); |
| } |
| return null; |
| } |
| |
| /** |
| * Find a state of the given name in the list of incoming transition of the |
| * given State. |
| */ |
| State findIncomingState(String name, State root) { |
| List<Transition> transitions = root.getIncomingTransitions(); |
| for (Transition t : transitions) { |
| if (t.getFromState().getName().equals(name)) |
| return t.getFromState(); |
| } |
| return null; |
| } |
| |
| /** |
| * Asserts the the origin state has the given incoming states. |
| */ |
| void assertIncomingStates(State origin, State... expected) { |
| assertNotNull(origin); |
| for (State e : expected) { |
| assertNotNull(e + " in not an incoimng states of " + origin, findIncomingState(e.getName(), origin)); |
| } |
| } |
| |
| /** |
| * Asserts the the origin state has the given outgoing states. |
| */ |
| void assertOutgoingStates(State origin, State... expected) { |
| assertNotNull(origin); |
| for (State e : expected) { |
| assertNotNull(e + " in not an incoimng states of " + origin, findOutgoingState(e.getName(), origin)); |
| } |
| } |
| } |