blob: fb0b2e2a571e6b0d498951cc4e11642613adf3c1 [file] [log] [blame]
/*
* 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.graph;
import java.sql.Date;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.FieldStrategy;
import org.apache.openjpa.jdbc.meta.MappingRepository;
import org.apache.openjpa.jdbc.meta.ValueHandler;
import org.apache.openjpa.jdbc.meta.strats.HandlerFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.HandlerHandlerMapTableFieldStrategy;
import org.apache.openjpa.jdbc.meta.strats.UntypedPCValueHandler;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* Tests basic create and query on generic persistent graph. The test creates a
* graph of People and Cities. Then different queries on the graph are verified.
*
* @author Pinaki Poddar
*
*/
public class TestPersistentGraph extends SingleEMFTestCase {
private static enum Emotion {
LOVES, HATES, KNOWS
}
// Identity of People is their SSN
private static final long[] SSN = { 123456781, 123456782, 123456783, 123456784, 123456785 };
private static final String[] PERSON_NAMES = { "P1", "P2", "P3", "P4", "P5" };
private static final String[] CITY_NAMES = { "San Francisco", "Paris", "Rome" };
private static final String ATTR_SINCE = "since";
private static final Date SINCE = new Date(90, 1, 27);
private static final String ATTR_EMOTION = "feels";
private static final Emotion[][] EMOTIONS = {
/* P1 P2 P3 P4 P5 */
/* P1 */new Emotion[] { null, Emotion.LOVES, Emotion.HATES, null, Emotion.KNOWS },
/* P2 */new Emotion[] { Emotion.LOVES, null, Emotion.LOVES, null, Emotion.LOVES },
/* P3 */new Emotion[] { Emotion.HATES, Emotion.LOVES, null, null, Emotion.KNOWS },
/* P4 */new Emotion[] { Emotion.LOVES, Emotion.HATES, Emotion.KNOWS, Emotion.LOVES, Emotion.LOVES },
/* P5 */new Emotion[] { null, Emotion.LOVES, Emotion.KNOWS, Emotion.KNOWS, null },
};
private static final String ATTR_DISTANCE = "distance";
private static final int[][] ATTR_DISTANCE_VALUE = {
/* C1 C2 C3 */
/* C1 */new int[] { 0, 200, 400 },
/* C2 */new int[] { 200, 0, 500 },
/* C3 */new int[] { 400, 500, 0 }
};
private EntityManager em;
private PersistentGraph<Object> graph;
@Override
public void setUp() throws Exception {
super.setUp(CLEAR_TABLES, PersistentGraph.class, RelationGraph.class,
PersistentRelation.class, People.class, City.class);
em = emf.createEntityManager();
graph = createData();
em.clear();
}
/**
* Verifies that fields are mapped with expected strategy or value handlers.
*/
public void testMapping() {
assertStrategy(PersistentRelation.class, "source", HandlerFieldStrategy.class, UntypedPCValueHandler.class);
assertStrategy(PersistentRelation.class, "target", HandlerFieldStrategy.class, UntypedPCValueHandler.class);
assertStrategy(PersistentRelation.class, "attrs", HandlerHandlerMapTableFieldStrategy.class, null);
}
private void printMapping(FieldMapping fm) {
System.err.println("Field :" + fm.getName());
System.err.println("Type :" + fm.getTypeCode() + " " + fm.getType());
System.err.println("Type (declared):" + fm.getDeclaredTypeCode() + " " + fm.getDeclaredType());
System.err.println("Type Override :" + fm.getTypeOverride());
System.err.println("Key type :" + fm.getKey().getType());
System.err.println("Key declared type :" + fm.getKey().getDeclaredType());
System.err.println("Element type :" + fm.getElement().getType());
System.err.println("Element declared type :" + fm.getElement().getDeclaredType());
}
FieldMapping getFieldMapping(Class<?> pcClass, String field) {
MappingRepository repos = (MappingRepository) emf.getConfiguration()
.getMetaDataRepositoryInstance();
ClassMapping cmd = repos.getMapping(pcClass, null, true);
assertNotNull("No metadata found for " + pcClass, cmd);
FieldMapping fmd = cmd.getFieldMapping(field);
assertNotNull("No metadata found for " + pcClass.getName() + "." + field + " Fields are "
+ Arrays.toString(cmd.getFieldNames()), fmd);
return fmd;
}
/**
* Asserts that the given field of the given class has been mapped with the
* given strategy or value handler.
*/
void assertStrategy(Class<?> pcClass, String field, Class<? extends FieldStrategy> strategy,
Class<? extends ValueHandler> handler) {
FieldMapping fmd = getFieldMapping(pcClass, field);
FieldStrategy actualStrategy = ((FieldMapping) fmd).getStrategy();
assertEquals(strategy, actualStrategy.getClass());
ValueHandler actualHandler = fmd.getHandler();
if (handler == null) {
if (actualHandler != null) {
printMapping(fmd);
fail("Expected no value handler for " + pcClass.getName() + "." + field +
" but found " + actualHandler);
}
} else {
if (actualHandler == null) {
printMapping(fmd);
fail("Expected a value handler for " + pcClass.getName() + "." + field + " but found null");
}
if (!handler.equals(actualHandler.getClass())) {
printMapping(fmd);
assertEquals(handler, fmd.getHandler().getClass());
}
}
}
FieldStrategy getStrategy(Class<?> cls, String field) {
MetaDataRepository repos = emf.getConfiguration().getMetaDataRepositoryInstance();
ClassMetaData cmd = repos.getMetaData(cls, null, true);
assertNotNull("No metadat found for " + cls, cmd);
FieldMetaData fmd = cmd.getField(field);
assertNotNull("No metadata found for " + cls.getName() + "." + field + " Fields are "
+ Arrays.toString(cmd.getFieldNames()), fmd);
FieldStrategy strategy = ((FieldMapping) fmd).getStrategy();
System.err.println(cls.getName() + "." + field + ":" + strategy.getClass().getSimpleName());
return strategy;
}
/**
* Tests that the nodes retrieved from the database meets the same
* assertions of the created graph.
*/
public void testCreateGraph() {
em.getTransaction().begin();
assertFalse(em.contains(graph));
graph = em.find(PersistentGraph.class, graph.getId());
assertNotNull(graph);
People[] people = new People[SSN.length];
for (int i = 0; i < SSN.length; i++) {
People p = em.find(People.class, SSN[i]);
assertNotNull(p);
people[i] = p;
}
City[] cities = new City[CITY_NAMES.length];
for (int i = 0; i < CITY_NAMES.length; i++) {
City c = em.find(City.class, CITY_NAMES[i]);
assertNotNull(c);
cities[i] = c;
}
assertDataEquals(graph, people, cities);
em.getTransaction().rollback();
}
/**
* Tests that relation can be queried and their references are set
* correctly.
*/
public void testQueryRelation() {
String jpql = "select r from PersistentRelation r";
List<PersistentRelation> relations = em.createQuery(jpql, PersistentRelation.class).getResultList();
for (Relation<?, ?> r : relations) {
Object source = r.getSource();
Object target = r.getTarget();
if (source instanceof People) {
int i = indexOf((People) source);
if (target instanceof People) {
int j = indexOf((People) target);
assertNotNull(EMOTIONS[i][j]);
assertEquals(EMOTIONS[i][j].toString(), r.getAttribute(ATTR_EMOTION));
} else if (target instanceof City) {
int j = indexOf((City) target);
assertEquals(i % CITY_NAMES.length, j);
assertTrue(r.getAttributes().isEmpty());
} else if (target != null){
fail("Unexpected relation " + r);
}
} else if (source instanceof City) {
int i = indexOf((City) source);
if (target instanceof City) {
int j = indexOf((City) target);
assertEquals(""+ATTR_DISTANCE_VALUE[i][j], r.getAttribute(ATTR_DISTANCE));
} else if (target != null) {
fail("Unexpected relation " + r);
}
}
}
}
/**
* Tests that a relation can be queried predicated on its source vertex.
*/
public void testQueryRelationOnSourceParameter() {
People p1 = em.find(People.class, SSN[0]);
String jpql = "select r from PersistentRelation r where r.source = :node";
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("node", p1)
.getResultList();
assertFalse("Result of [" + jpql + "] on source = " + p1 + " should not be empty", result.isEmpty());
}
/**
* Tests that a relation can be queried predicated on its attribute key.
*/
public void testQueryRelationOnSingleAttributeKey() {
String jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key";
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", ATTR_EMOTION)
.getResultList();
assertFalse("Result of [" + jpql + "] on key = " + ATTR_EMOTION + " should not be empty", result.isEmpty());
}
/**
* Tests that a relation can be queried predicated on a single attribute
* key-value pair.
*/
public void testQueryRelationOnSingleAttributeKeyValue() {
String jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key and value(a) = :value";
String value = EMOTIONS[0][2].toString();
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", ATTR_EMOTION)
.setParameter("value", value)
.getResultList();
assertFalse("Result of [" + jpql + "] on key-value (" + ATTR_EMOTION + "," + value + ") should not be empty",
result.isEmpty());
}
/**
* Tests that a relation can be queried predicated on a multiple attribute
* key-value pair. This requires multiple joins. Single join will produce
* wrong result.
*/
public void testQueryRelationOnMultipleAttributeKeyValuePairs() {
String jpql = "select r from PersistentRelation r join r.attrs a1 join r.attrs a2 "
+ "where key(a1) = :key1 and value(a1) = :value1 "
+ "and key(a2) = :key2 and value(a2) = :value2";
String value = EMOTIONS[0][2].toString();
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key1", ATTR_EMOTION)
.setParameter("value1", value)
.setParameter("key2", ATTR_SINCE)
.setParameter("value2", SINCE.toString())
.getResultList();
assertFalse("Result of [" + jpql + "] on key-value = (" + ATTR_EMOTION + "," + value
+ ") and key-value=(" + ATTR_SINCE + "," + SINCE + ") should not be empty",
result.isEmpty());
String wrongJPQL = "select r from PersistentRelation r join r.attrs a "
+ "where key(a) = :key1 and value(a) = :value1 "
+ "and key(a) = :key2 and value(a) = :value2";
List<PersistentRelation> result2 = em.createQuery(wrongJPQL, PersistentRelation.class)
.setParameter("key1", ATTR_EMOTION)
.setParameter("value1", value)
.setParameter("key2", ATTR_SINCE)
.setParameter("value2", SINCE.toString())
.getResultList();
assertTrue("Result of [" + jpql + "] on key-value = (" + ATTR_EMOTION + "," + value
+ ") and key-value=("+ ATTR_SINCE + "," + SINCE + ") should be empty",
result2.isEmpty());
}
public void testAddRemoveAttribute() {
em.getTransaction().begin();
People p1 = em.find(People.class, SSN[0]);
String jpql = "select r from PersistentRelation r where r.source = :node";
List<PersistentRelation> r = em.createQuery(jpql, PersistentRelation.class)
.setHint(QueryHints.HINT_IGNORE_PREPARED_QUERY, true)
.setParameter("node", p1)
.getResultList();
assertFalse(r.isEmpty());
r.get(0).addAttribute("new-key", "new-value");
em.getTransaction().commit();
em.clear();
em.getTransaction().begin();
jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key";
Relation newR = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", "new-key")
.getSingleResult();
assertNotNull(newR);
assertEquals("new-value", newR.getAttribute("new-key"));
newR.removeAttribute("new-key");
em.getTransaction().commit();
em.getTransaction().begin();
jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key";
try {
newR = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", "new-key")
.getSingleResult();
fail(jpql + " with new-key expected no result");
} catch (NoResultException nre) {
// this is what is expected
} finally {
em.getTransaction().rollback();
}
}
boolean isPopulated() {
return em.createQuery("select count(p) from People p", Long.class).getSingleResult() > 0;
}
/**
* Creates a typical graph of People and Cities. The tests are sensitive to
* the actual values and relations set in in this method.
*/
PersistentGraph<Object> createData() {
PersistentGraph<Object> graph = new RelationGraph<>();
em.getTransaction().begin();
People[] people = new People[SSN.length];
for (int i = 0; i < SSN.length; i++) {
People p = new People();
graph.add(p);
p.setSsn(SSN[i]);
p.setName(PERSON_NAMES[i]);
people[i] = p;
}
City[] cities = new City[CITY_NAMES.length];
for (int i = 0; i < CITY_NAMES.length; i++) {
City c = new City();
graph.add(c);
c.setName(CITY_NAMES[i]);
cities[i] = c;
}
for (int i = 0; i < people.length; i++) {
for (int j = 0; j < people.length; j++) {
if (EMOTIONS[i][j] != null) {
Relation<People, People> r = graph.link(people[i], people[j])
.addAttribute(ATTR_EMOTION, EMOTIONS[i][j]);
if (i == 0 && j == 2) {
r.addAttribute(ATTR_SINCE, SINCE);
}
}
}
}
for (int i = 0; i < cities.length; i++) {
for (int j = 0; j < cities.length; j++) {
graph.link(cities[i], cities[j]).addAttribute(ATTR_DISTANCE, ATTR_DISTANCE_VALUE[i][j]);
}
}
for (int i = 0; i < people.length; i++) {
graph.link(people[i], cities[i % CITY_NAMES.length]);
}
em.persist(graph);
em.getTransaction().commit();
return graph;
}
void assertDataEquals(Graph<Object> graph, People[] people, City[] cities) {
assertEquals(SSN.length, people.length);
assertEquals(CITY_NAMES.length, cities.length);
for (int i = 0; i < people.length; i++) {
People p = people[i];
assertEquals(SSN[i], p.getSsn());
assertEquals(PERSON_NAMES[i], p.getName());
}
for (int i = 0; i < cities.length; i++) {
City c = cities[i];
assertEquals(CITY_NAMES[i], c.getName());
}
for (int i = 0; i < people.length; i++) {
People p1 = people[i];
for (int j = 0; j < people.length; j++) {
People p2 = people[j];
Relation<People, People> r = graph.getRelation(p1,p2);
if (EMOTIONS[i][j] != null) {
assertNotNull(r);
assertEquals(EMOTIONS[i][j].toString(), r.getAttribute(ATTR_EMOTION));
} else {
assertNull(r);
}
}
}
for (int i = 0; i < cities.length; i++) {
City c1 = cities[i];
for (int j = 0; j < cities.length; j++) {
City c2 = cities[j];
Relation<City, City> r12 = graph.getRelation(c1,c2);
assertNotNull(r12);
assertEquals(""+ATTR_DISTANCE_VALUE[i][j], r12.getAttribute(ATTR_DISTANCE));
}
}
for (int i = 0; i < people.length; i++) {
People p = people[i];
for (int j = 0; j < cities.length; j++) {
City c = cities[j];
Relation<People, City> r = graph.getRelation(p,c);
if (i % CITY_NAMES.length == j) {
assertNotNull(r);
} else {
assertNull(r);
}
}
}
}
int indexOf(People p) {
for (int i = 0; i < SSN.length; i++) {
if (SSN[i] == p.getSsn())
return i;
}
return -1;
}
int indexOf(City c) {
for (int i = 0; i < CITY_NAMES.length; i++) {
if (CITY_NAMES[i].equals(c.getName()))
return i;
}
return -1;
}
}