OPENJPA-1686: Define generic persistent graph and relation. Implement graph as a set of edges.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@955425 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2010-06-16 23:22:31 +00:00
parent 2e827c5eab
commit a79a96b86c
10 changed files with 667 additions and 341 deletions

View File

@ -0,0 +1,47 @@
/*
* 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.util.AbstractSet;
import java.util.Set;
/**
* Abstract implementation of a {@linkplain Graph} borrows from {@link AbstractSet abstract} implementation of
* {@link Set}. The extended {@link Set#remove(Object) remove()} semantics accounts for
* {@link Graph#delink(Object, Object) removal} of all relationship to the removed element.
*
* @author Pinaki Poddar
*
* @param <E> type of element of the graph.
*/
public abstract class AbstractGraph<E> extends AbstractSet<E> implements Graph<E> {
/**
* Removing an element from this graph has the side effect of removing all
* relations directed to the removed element.
*/
@Override
public boolean remove(Object e) {
E node = (E)e;
Set<Relation<E, E>> rs = getRelationsTo(node);
for (Relation<E,E> r : rs) {
delink(r.getSource(), node);
}
return super.remove(e);
}
}

View File

@ -18,20 +18,20 @@
*/
package org.apache.openjpa.persistence.graph;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* A simple persistent entity to become member of a graph.
* In this style, a type has to extend {@linkplain Vertex} - an abstract persistent type.
* This persistent type has its own identity.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class City extends Vertex<City> {
public class City implements Serializable {
@Id
private String name;

View File

@ -0,0 +1,109 @@
/*
* 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.util.Set;
/**
* Graph is an extended {@link java.util.Set Set} that is aware of {@link Relation relationship}
* between its elements. The linkage between two elements is represented by a {@link Relation relation}.
* <br>
* The extended behavior of Set allows two member elements to be {@linkplain Graph#link(Object, Object) linked}
* or {@linkplain Graph#delink(Object, Object) delinked}.
*
* @param <E> Type of element of the graph
*
*
* @author Pinaki Poddar
*
*/
public interface Graph<E> extends Set<E> {
/**
* Links the pair of given vertices.
* If the pair is already linked then the existing relation is returned.
* If either of the vertices are currently non a member of this graph,
* then they are added to the graph as a side-effect of linking.
*
* @param source non-null source node
* @param target non-null target node
*
* @param <V1> generic type of source node
* @param <V2> generic type of target node
*
* @return a relation
*/
public <V1 extends E, V2 extends E> Relation<V1, V2> link(V1 source, V2 target);
/**
* Breaks the relation between the given pair of nodes.
*
* @param source non-null source node
* @param target non-null target node
*
* @param <V1> generic type of source node
* @param <V2> generic type of target node
*
* @return the existing relation, if any, that had been broken. null otherwise.
*/
public <V1 extends E, V2 extends E> Relation<V1, V2> delink(V1 source, V2 target);
/**
* Gets the directed relation between the given pair of nodes, if exists.
*
* @param source non-null source node
* @param target non-null target node
*
* @param <V1> generic type of source node
* @param <V2> generic type of target node
*
* @return a relation between the nodes, if exists. null otherwise.
*/
public <V1 extends E, V2 extends E> Relation<V1, V2> getRelation(V1 source, V2 target);
/**
* Gets the nodes that are directly reachable from the given source node.
*
* @return set of target nodes. Empty set if the given source node is not connected to any other nodes.
*/
public Set<E> getTargets(E source);
/**
* Gets the source nodes that are directly connected to the given target node.
*
* @return set of source nodes. Empty set if the given target node is not connected from any node.
*/
public Set<E> getSources(E target);
/**
* Gets all the relations originating from the given source.
* @param <V>
* @param source
* @return
*/
public <V extends E> Set<Relation<V,E>> getRelationsFrom(V source);
/**
* Gets all the relations terminating on the given target.
* @param <V>
* @param target
* @return
*/
public <V extends E> Set<Relation<E,V>> getRelationsTo(V target);
}

View File

@ -18,23 +18,24 @@
*/
package org.apache.openjpa.persistence.graph;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* A simple persistent entity to become member of a graph.
* In this style, a type has to extend {@linkplain Vertex} - an abstract persistent type.
* This persistent type has its own identity.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class People extends Vertex<People>{
public class People implements Serializable {
@Id
private long ssn;
private String name;
public long getSsn() {
return ssn;
}

View File

@ -0,0 +1,53 @@
/*
* 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.io.Serializable;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
* Persistent Graph is a marker for persistent version of a {@link Graph}.
* <br>
* A persistent graph does not declare how the persistent state of the graph is captured and hence is abstract.
* But Persistent Graph is unique to be persistent in a JPA sense, because JPA does <em>not</em> represent
* container instances such as {@link java.util.Set} or {@link java.util.List} as <em>first class</em> type --
* i.e. their instances do not carry a persistent identity. But declaring a graph as persistent type amounts
* to represent the graph as a first class persistent type with its own persistent identity.
* <br>
* Persistent Graph defines a auto-generated persistent identity.
*
* @author Pinaki Poddar
*
* @param <E> type of element.
*/
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class PersistentGraph<E> extends AbstractGraph<E> implements Serializable {
@Id
@GeneratedValue
private long id;
public final long getId() {
return id;
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.io.Serializable;
import java.util.Properties;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
/**
* Generic, directed, attributed Relation as a first-class, persistent entity.
* <br>
* A relation is
* <ol>
* <LI>generic because Relation type is parameterized with the type of vertices it links.
* <LI>directed because it distinguishes the two vertices as source and target.
* <LI>attributed because any arbitrary name-value pair can be associated with a relation.
* </ol>
* A relation is made persistence capable by annotating its generic source and target as persistent entity.
* A relation is also a <em>first-class</em> entity having its own persistent identifier.
* <br>
* A relation is immutable in terms of its two vertices. The properties
* attached to a relation, however, can change.
* <br>
* @param <V1> the type of <em>source</em> vertex linked by this relation.
* @param <V2> the type of <em>target</em> vertex linked by this relation.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class PersistentRelation<V1,V2> implements Relation<V1,V2>, Serializable {
/**
* Relation is a first class object with its own identifier.
*/
@Id
@GeneratedValue
private long id;
/**
* A Relation must have a non-null vertex as source.
*/
@OneToOne(optional=false, targetEntity=Entity.class,
cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private V1 source;
/**
* A Relation may have a non-null vertex as target.
*/
@OneToOne(optional=true, targetEntity=Entity.class,
cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private V2 target;
/**
* The properties of a Relation is a set of key-value pairs and is declared as
* <code>java.util.Properties</code>.
* <br>
* Declaring the key-value pairs as <code>java.util.Properties</code> makes OpenJPA
* assume that both key and value will be stored in database as String.
* This is not <em>strictly</em> correct because <code>java.util.Properties</code>
* declares its key and value as <code>java.lang.Object</code>. Hence it is possible for an application
* to insert key and/or value that are not a String but that type information will not be preserved in
* the database. Subsequently, when loaded from database the key and value
* both will appear as String and hence it becomes the application's responsibility to decode the
* Strings back to the actual type. While this provision loses type information, it allows the
* database record to be readable and more importantly supports query that are predicated on
* (equality only) key-value pairs.
* <br>
* Another possibility to express key-value pair as
* <br>
* <code>Map<String,Serializable> attrs;</code>
* <br>
* This will serialize the values but preserve their types. The down-side is neither a query can be
* predicated on value nor are the database records readable.
* <br>
* The third alternative is a Map where keys are String and values are Object
* <br>
* <code>Map<String,Object> attrs;</code>
* This leads to the whole map being serialized as a single blob of data.
*/
@ManyToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY)
private Properties attrs;
/**
* Special constructor for byte code enhancement.
*/
protected PersistentRelation() {
}
/**
* A relation is immutable in terms of two vertices it connects.
*
* @param s source vertex must not be null.
* @param t target vertex may or may not be null.
*/
public PersistentRelation(V1 s, V2 t) {
if (s == null)
throw new NullPointerException("Can not create relation from a null source vertex");
source = s;
target = t;
attrs = new Properties();
}
/**
* Gets generated persistent identity.
*/
public long getId() {
return id;
}
/**
* Gets the immutable source vertex.
*/
public V1 getSource() {
return source;
}
/**
* Gets the immutable target vertex.
*/
public V2 getTarget() {
return target;
}
/**
* Affirms if the given attribute is associated with this relation.
*/
public boolean hasAttribute(String attr) {
return attrs.containsKey(attr);
}
/**
* Gets the value of the given attribute.
*
* @return value of the given attribute. A null value does not distinguish whether
* the attribute was set to a null value or the attribute was absent.
*/
public Object getAttribute(String attr) {
return attrs.get(attr);
}
public Properties getAttributes() {
return attrs;
}
/**
* Adds the given key-value pair, overwriting any prior association to the same attribute.
*
* @return the same relation for fluent method-chaining
*/
public Relation<V1,V2> addAttribute(String attr, Object v) {
attrs.put(attr, v);
return this;
}
/**
* Removes the given attribute.
*
* @return value of the given attribute that just has been removed. A null value does not
* distinguish whether the attribute was set to a null value or the attribute was absent.
*/
public Relation<V1,V2> removeAttribute(String attr) {
attrs.remove(attr);
return this;
}
public String toString() {
return source + "->" + target;
}
}

View File

@ -18,167 +18,76 @@
*/
package org.apache.openjpa.persistence.graph;
import java.io.Serializable;
import java.util.Properties;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
/**
* Generic, directed, attributed Relation as a first-class entity.
* Generic, directed, attributed Relation.
* <br>
* A relation is
* <ol>
* <LI>generic because the vertices it links are generically typed.
* <LI>directed because it distinguishes the two end points as source and target.
* <LI>attributed because any arbitrary name-value pair can be associated with a relation.
* <LI>generic because Relation type is parameterized with the type of vertices it links.
* <LI>directed because it distinguishes the two vertices as source and target.
* <LI>attributed because any arbitrary key-value pair can be associated with a relation.
* </ol>
* <br>
* A relation is immutable in terms of its two vertices. The properties
* attached to a relation can change.
*
* associated to a relation, however, can change.
* <br>
* @param <V1> the type of <em>source</em> vertex linked by this relation.
* @param <V2> the type of <em>target</em> vertex linked by this relation.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Relation<V1,V2> implements Serializable {
/**
* Relation is a first class object with its own identifier.
*/
@Id
@GeneratedValue
private long id;
/**
* A Relation must have a non-null vertex as source.
*/
@OneToOne(optional=false)
private Vertex<V1> source;
/**
* A Relation must have a non-null vertex as source.
*/
@OneToOne(optional=false)
private Vertex<V2> target;
/**
* The properties of a Relation is a set of key-value pairs and is declared as
* <code>java.util.Properties</code>.
* <br>
* Declaring the key-value pairs as <code>java.util.Properties</code> makes OpenJPA
* assume that both key and value will be stored in database as String.
* This is not <em>strictly</em> correct because <code>java.util.Properties</code>
* declares its key and value as <code>java.lang.Object</code>. Hence it is possible for an application
* to insert key and/or value that are not a String but that type information will not be preserved in
* the database. Subsequently, when loaded from database the key and value
* both will appear as String and hence it becomes the application's responsibility to decode the
* Strings back to the actual type. While this provision loses type information, it allows the
* database record to be readable and more importantly supports query that are predicated on
* (equality only) key-value pairs.
* <br>
* Another possibility to express key-value pair as
* <br>
* <code>Map<String,Serializable> attrs;</code>
* <br>
* This will serialize the values but preserve their types. The down-side is neither a query can be
* predicated on value nor are the database records readable.
* <br>
* The third alternative is a Map where keys are String and values are Object
* <br>
* <code>Map<String,Object> attrs;</code>
* This leads to the whole map being serialized as a single blob of data.
*/
@ManyToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY)
private Properties attrs;
/**
* Special constructor for byte code enhancement.
*/
protected Relation() {
}
/**
* A relation is immutable in terms of two vertices it connects.
* Either vertex must not be null.
*/
public Relation(Vertex<V1> s, Vertex<V2> t) {
if (s == null)
throw new NullPointerException("Can not create relation from a null source vertex");
if (t == null)
throw new NullPointerException("Can not create relation to a null target vertex");
source = s;
target = t;
attrs = new Properties();
}
/**
* Gets generated persistent identity.
*/
public long getId() {
return id;
}
public interface Relation<V1,V2> {
/**
* Gets the immutable source vertex.
*/
public Vertex<V1> getSource() {
return source;
}
*
* @return a non-null source vertex.
*/
public V1 getSource();
/**
* Gets the immutable target vertex.
*/
public Vertex<V2> getTarget() {
return target;
}
* Unlike source, a target for a relation may be null.
*
* @return a target vertex. May be null.
*/
public V2 getTarget();
/**
* Affirms if the given attribute is associated with this relation.
* Adds the given key-value pair, overwriting any prior value associated to the same key.
*
* @return the same relation for <em>fluent</em> method-chaining
*/
public boolean hasAttribute(String attr) {
return attrs.containsKey(attr);
}
public Relation<V1,V2> addAttribute(String key, Object value);
/**
* Affirms if an attribute value has been associated with the given key.
*
*/
public boolean hasAttribute(String key);
/**
* Gets the value of the given attribute.
*
* @return value of the given attribute. A null value does not distinguish whether
* the attribute was set to a null value or the attribute was absent.
*/
public Object getAttribute(String attr) {
return attrs.get(attr);
}
public Properties getAttributes() {
return attrs;
}
/**
* Adds the given key-value pair, overwriting any prior association to the same attribute.
*
* @return the same relation for fluent method-chaining
* @see #hasAttribute(String)
*/
public Relation<V1,V2> addAttribute(String attr, Object v) {
attrs.put(attr, v);
return this;
}
public Object getAttribute(String key);
/**
* Removes the given attribute.
*
* @return value of the given attribute that just has been removed. A null value does not
* distinguish whether the attribute was set to a null value or the attribute was absent.
* @return the modified relation for <em>fluent</em> method chaining.
*/
public Relation<V1,V2> removeAttribute(String attr) {
attrs.remove(attr);
return this;
}
public Relation<V1,V2> removeAttribute(String key);
/**
* Gets the key-value pairs associated with this relation.
*/
public Properties getAttributes();
}

View File

@ -0,0 +1,163 @@
/*
* 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.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
/**
* RelationGraph is a first-class persistent entity that express its persistent state as a set of
* {@link Relation persistent relations}.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class RelationGraph<E> extends PersistentGraph<E> {
@OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
private Set<PersistentRelation<E,E>> relations = new HashSet<PersistentRelation<E,E>>();
/*
* Links the given vertices, unless they are already connected.
*
* @param source non-null source vertex
* @param target non-null target vertex
*
* @see org.apache.openjpa.persistence.graph.Graph#link(V1, V2)
*/
public <V1 extends E,V2 extends E> Relation<V1,V2> link(V1 source, V2 target) {
if (source == null)
throw new NullPointerException("Can not link from a null source vertex");
if (target == null)
throw new NullPointerException("Can not link to a null target vertex");
Relation<V1,V2> r = getRelation(source, target);
if (r == null) {
r = new PersistentRelation<V1, V2>(source, target);
relations.add((PersistentRelation<E, E>) r);
}
return r;
}
/*
* Delinks the given vertices, if they are currently connected.
*
*
* @see org.apache.openjpa.persistence.graph.Graph#delink(V1, V2)
*/
public <V1 extends E,V2 extends E> Relation<V1,V2> delink(V1 source, V2 target) {
Relation<V1,V2> r = getRelation(source, target);
if (r != null) {
relations.remove(r);
}
return r;
}
/*
* Get the relation between the given vertex.
*
* @see org.apache.openjpa.persistence.graph2.Graph#getRelation(V1, V2)
*/
public <V1 extends E,V2 extends E> Relation<V1,V2> getRelation(V1 source, V2 target) {
for (Relation<?,?> r : relations) {
if (r.getSource().equals(source) && r.getTarget() != null && r.getTarget().equals(target)) {
return (Relation<V1,V2>)r;
}
}
return null;
}
/**
* Iterator over the nodes of this graph.
*/
public Iterator<E> iterator() {
return getNodes().iterator();
}
public int size() {
return 0;
}
@Override
public <V extends E> Set<Relation<V, E>> getRelationsFrom(V source) {
Set<Relation<V,E>> rs = new HashSet<Relation<V,E>>();
for (Relation<E,E> r : relations) {
if (r.getSource().equals(source) && r.getTarget() != null)
rs.add((Relation<V,E>)r);
}
return rs;
}
@Override
public <V extends E> Set<Relation<E, V>> getRelationsTo(V target) {
Set<Relation<E, V>> rs = new HashSet<Relation<E, V>>();
for (Relation<?,?> r : relations) {
if (r.getTarget() != null && r.getTarget().equals(target))
rs.add((Relation<E, V>)r);
}
return rs;
}
@Override
public Set<E> getSources(Object target) {
Set<E> sources = new HashSet<E>();
for (Relation<E,E> r : relations) {
if (r.getTarget() != null && r.getTarget().equals(target))
sources.add(r.getSource());
}
return sources;
}
@Override
public Set<E> getTargets(Object source) {
Set<E> targets = new HashSet<E>();
for (Relation<E,E> r : relations) {
if (r.getSource().equals(source) && r.getTarget() != null)
targets.add(r.getTarget());
}
return targets;
}
public Set<E> getNodes() {
Set all = new HashSet();
for (Relation<?,?> r : relations) {
all.add(r.getSource());
if (r.getTarget() != null)
all.add(r.getTarget());
}
return all;
}
@Override
public boolean add(E e) {
if (contains(e))
return false;
relations.add(new PersistentRelation<E,E>(e,null));
return true;
}
}

View File

@ -32,13 +32,11 @@ 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.RelationCollectionInverseKeyFieldStrategy;
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.OpenJPAPersistence;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
@ -80,23 +78,23 @@ public class TestPersistentGraph extends SingleEMFTestCase {
};
private EntityManager em;
private PersistentGraph<Object> graph;
public void setUp() throws Exception {
super.setUp(CLEAR_TABLES, Vertex.class, Relation.class, People.class, City.class);
super.setUp(CLEAR_TABLES, PersistentGraph.class, RelationGraph.class,
PersistentRelation.class, People.class, City.class);
em = emf.createEntityManager();
createData();
graph = createData();
em.clear();
}
/**
* Verifies that fields are mapped with expected strategy or value handlers.
*/
public void testMapping() {
assertStrategy(People.class, "relations", RelationCollectionInverseKeyFieldStrategy.class, null);
assertStrategy(City.class, "relations", RelationCollectionInverseKeyFieldStrategy.class, null);
assertStrategy(Relation.class, "source", HandlerFieldStrategy.class, UntypedPCValueHandler.class);
assertStrategy(Relation.class, "target", HandlerFieldStrategy.class, UntypedPCValueHandler.class);
assertStrategy(Relation.class, "attrs", HandlerHandlerMapTableFieldStrategy.class, null);
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) {
@ -111,7 +109,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
}
FieldMapping getFieldMapping(Class<?> pcClass, String field) {
MappingRepository repos = (MappingRepository) OpenJPAPersistence.cast(emf).getConfiguration()
MappingRepository repos = (MappingRepository) emf.getConfiguration()
.getMetaDataRepositoryInstance();
ClassMapping cmd = repos.getMapping(pcClass, null, true);
assertNotNull("No metadata found for " + pcClass, cmd);
@ -123,7 +121,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
/**
* Asserts that the given field of the given class has been mapped with the
* given straegy or value handler.
* given strategy or value handler.
*/
void assertStrategy(Class<?> pcClass, String field, Class<? extends FieldStrategy> strategy,
Class<? extends ValueHandler> handler) {
@ -151,7 +149,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
}
FieldStrategy getStrategy(Class<?> cls, String field) {
MetaDataRepository repos = OpenJPAPersistence.cast(emf).getConfiguration().getMetaDataRepositoryInstance();
MetaDataRepository repos = emf.getConfiguration().getMetaDataRepositoryInstance();
ClassMetaData cmd = repos.getMetaData(cls, null, true);
assertNotNull("No metadat found for " + cls, cmd);
FieldMetaData fmd = cmd.getField(field);
@ -168,6 +166,9 @@ public class TestPersistentGraph extends SingleEMFTestCase {
*/
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]);
@ -180,7 +181,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
assertNotNull(c);
cities[i] = c;
}
assertDataEquals(people, cities);
assertDataEquals(graph, people, cities);
em.getTransaction().rollback();
}
@ -190,10 +191,11 @@ public class TestPersistentGraph extends SingleEMFTestCase {
* correctly.
*/
public void testQueryRelation() {
List<Relation> relations = em.createQuery("select r from Relation r", Relation.class).getResultList();
String jpql = "select r from PersistentRelation r";
List<PersistentRelation> relations = em.createQuery(jpql, PersistentRelation.class).getResultList();
for (Relation<?, ?> r : relations) {
Vertex<?> source = r.getSource();
Vertex<?> target = r.getTarget();
Object source = r.getSource();
Object target = r.getTarget();
if (source instanceof People) {
int i = indexOf((People) source);
if (target instanceof People) {
@ -204,16 +206,16 @@ public class TestPersistentGraph extends SingleEMFTestCase {
int j = indexOf((City) target);
assertEquals(i % CITY_NAMES.length, j);
assertTrue(r.getAttributes().isEmpty());
} else {
fail();
} 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 {
fail();
} else if (target != null) {
fail("Unexpected relation " + r);
}
}
}
@ -224,8 +226,8 @@ public class TestPersistentGraph extends SingleEMFTestCase {
*/
public void testQueryRelationOnSourceParameter() {
People p1 = em.find(People.class, SSN[0]);
String jpql = "select r from Relation r where r.source = :node";
List<Relation> result = em.createQuery(jpql, Relation.class)
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());
@ -235,8 +237,8 @@ public class TestPersistentGraph extends SingleEMFTestCase {
* Tests that a relation can be queried predicated on its attribute key.
*/
public void testQueryRelationOnSingleAttributeKey() {
String jpql = "select r from Relation r join r.attrs a where key(a) = :key";
List<Relation> result = em.createQuery(jpql, Relation.class)
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();
@ -248,9 +250,9 @@ public class TestPersistentGraph extends SingleEMFTestCase {
* key-value pair.
*/
public void testQueryRelationOnSingleAttributeKeyValue() {
String jpql = "select r from Relation r join r.attrs a where key(a) = :key and value(a) = :value";
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<Relation> result = em.createQuery(jpql, Relation.class)
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", ATTR_EMOTION)
.setParameter("value", value)
.getResultList();
@ -265,10 +267,11 @@ public class TestPersistentGraph extends SingleEMFTestCase {
* wrong result.
*/
public void testQueryRelationOnMultipleAttributeKeyValuePairs() {
String jpql = "select r from Relation 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 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<Relation> result = em.createQuery(jpql, Relation.class)
List<PersistentRelation> result = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key1", ATTR_EMOTION)
.setParameter("value1", value)
.setParameter("key2", ATTR_SINCE)
@ -279,10 +282,10 @@ public class TestPersistentGraph extends SingleEMFTestCase {
+ ") and key-value=(" + ATTR_SINCE + "," + SINCE + ") should not be empty",
result.isEmpty());
String wrongJPQL = "select r from Relation r join r.attrs a "
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<Relation> result2 = em.createQuery(wrongJPQL, Relation.class)
List<PersistentRelation> result2 = em.createQuery(wrongJPQL, PersistentRelation.class)
.setParameter("key1", ATTR_EMOTION)
.setParameter("value1", value)
.setParameter("key2", ATTR_SINCE)
@ -297,8 +300,8 @@ public class TestPersistentGraph extends SingleEMFTestCase {
public void testAddRemoveAttribute() {
em.getTransaction().begin();
People p1 = em.find(People.class, SSN[0]);
String jpql = "select r from Relation r where r.source = :node";
List<Relation> r = em.createQuery(jpql, Relation.class)
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();
@ -308,8 +311,8 @@ public class TestPersistentGraph extends SingleEMFTestCase {
em.clear();
em.getTransaction().begin();
jpql = "select r from Relation r join r.attrs a where key(a) = :key";
Relation newR = em.createQuery(jpql, Relation.class)
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);
@ -318,9 +321,9 @@ public class TestPersistentGraph extends SingleEMFTestCase {
em.getTransaction().commit();
em.getTransaction().begin();
jpql = "select r from Relation r join r.attrs a where key(a) = :key";
jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key";
try {
newR = em.createQuery(jpql, Relation.class)
newR = em.createQuery(jpql, PersistentRelation.class)
.setParameter("key", "new-key")
.getSingleResult();
fail(jpql + " with new-key expected no result");
@ -339,15 +342,15 @@ public class TestPersistentGraph extends SingleEMFTestCase {
* Creates a typical graph of People and Cities. The tests are sensitive to
* the actual values and relations set in in this method.
*/
void createData() {
if (isPopulated())
return;
PersistentGraph<Object> createData() {
PersistentGraph<Object> graph = new RelationGraph<Object>();
em.getTransaction().begin();
People[] people = new People[SSN.length];
for (int i = 0; i < SSN.length; i++) {
People p = new People();
em.persist(p);
graph.add(p);
p.setSsn(SSN[i]);
p.setName(PERSON_NAMES[i]);
people[i] = p;
@ -355,14 +358,15 @@ public class TestPersistentGraph extends SingleEMFTestCase {
City[] cities = new City[CITY_NAMES.length];
for (int i = 0; i < CITY_NAMES.length; i++) {
City c = new City();
em.persist(c);
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 = people[i].link(people[j]).addAttribute(ATTR_EMOTION, EMOTIONS[i][j]);
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);
}
@ -371,18 +375,20 @@ public class TestPersistentGraph extends SingleEMFTestCase {
}
for (int i = 0; i < cities.length; i++) {
for (int j = 0; j < cities.length; j++) {
cities[i].link(cities[j]).addAttribute(ATTR_DISTANCE, ATTR_DISTANCE_VALUE[i][j]);
graph.link(cities[i], cities[j]).addAttribute(ATTR_DISTANCE, ATTR_DISTANCE_VALUE[i][j]);
}
}
for (int i = 0; i < people.length; i++) {
people[i].link(cities[i % CITY_NAMES.length]);
graph.link(people[i], cities[i % CITY_NAMES.length]);
}
em.persist(graph);
em.getTransaction().commit();
return graph;
}
void assertDataEquals(People[] people, City[] cities) {
void assertDataEquals(Graph<Object> graph, People[] people, City[] cities) {
assertEquals(SSN.length, people.length);
assertEquals(CITY_NAMES.length, cities.length);
@ -399,7 +405,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
People p1 = people[i];
for (int j = 0; j < people.length; j++) {
People p2 = people[j];
Relation<People, People> r = p1.getRelationTo(p2);
Relation<People, People> r = graph.getRelation(p1,p2);
if (EMOTIONS[i][j] != null) {
assertNotNull(r);
assertEquals(EMOTIONS[i][j].toString(), r.getAttribute(ATTR_EMOTION));
@ -412,7 +418,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
City c1 = cities[i];
for (int j = 0; j < cities.length; j++) {
City c2 = cities[j];
Relation<City, City> r12 = c1.getRelationTo(c2);
Relation<City, City> r12 = graph.getRelation(c1,c2);
assertNotNull(r12);
assertEquals(""+ATTR_DISTANCE_VALUE[i][j], r12.getAttribute(ATTR_DISTANCE));
}
@ -422,7 +428,7 @@ public class TestPersistentGraph extends SingleEMFTestCase {
People p = people[i];
for (int j = 0; j < cities.length; j++) {
City c = cities[j];
Relation<People, City> r = p.getRelationTo(c);
Relation<People, City> r = graph.getRelation(p,c);
if (i % CITY_NAMES.length == j) {
assertNotNull(r);
} else {

View File

@ -1,155 +0,0 @@
/*
* 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.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToMany;
/**
* Vertex of a persistent graph.
* <br>
* A vertex maintains relationship to other connected vertices.
* An abstract vertex provides the functionality for a concrete derivation to be member of a graph.
* <br>
* A generic vertex does <em>not</em> define a persistent identity on the derived types.
* <br>
* The only persistent state maintained by a generic vertex is its ({@linkplain Relation relations}
* to its neighboring vertices.
* <br>
* @author Pinaki Poddar
*
* @param <V> the type of this vertex.
*/
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class Vertex<V> implements Serializable {
/**
* A set of relations starting from this vertex.
* A vertex owns its relations in a object modeling sense but not in a JPA sense.
* In a object modeling sense, a relation can not exist without its source vertex.
* In a JPA sense, the relational table for a Relation holds a foreign key to the source vertex,
* so Relation is the owner of vertex-Relation relation, confusing, eh?
*/
@OneToMany(mappedBy="source",
cascade=CascadeType.ALL,
orphanRemoval=true,
targetEntity=Relation.class)
private Set<Relation<V,?>> relations;
/**
* Create a relation to the given vertex from this vertex, if no such relation exists.
* If a relation exists, returns the existing relation.
*
* @param n a non-null vertex.
*/
public <V2> Relation<V,V2> link(Vertex<V2> n) {
if (n == null) {
throw new NullPointerException(this + " can not link to null target");
}
Relation<V,V2> r = getRelationTo(n);
if (r == null) {
r = new Relation<V,V2>(this, n);
if (relations == null) {
relations = new HashSet<Relation<V,?>>();
}
relations.add(r);
}
return r;
}
/**
* Breaks the relation, if exists, from this vertex to the given vertex.
* Returns the broken link.
*
* @param n a vertex, possibly null.
*/
public <V2> Relation<V,V2> delink(Vertex<V2> n) {
Relation<V,V2> r = getRelationTo(n);
if (r != null) {
relations.remove(r);
}
return r;
}
/**
* Gets the relation from this vertex to the given vertex, if exists. Null otherwise.
*
* @param n a vertex, possibly null.
*/
public <V2> Relation<V,V2> getRelationTo(Vertex<V2> n) {
if (n == null || relations == null)
return null;
for (Relation<V,?> r : relations) {
if (r.getTarget().equals(n)) {
return (Relation<V,V2>)r;
}
}
return null;
}
/**
* Affirms if the given vertex is linked from this vertex.
*/
public boolean isRelatedTo(Vertex<V> n) {
return getRelationTo(n) != null;
}
/**
* Gets all the relations starting from this vertex.
*/
public Set<Relation<V,?>> getRelations() {
return relations;
}
/**
* Gets all the immediate neighbors.
*/
public Set<Vertex<?>> getNeighbours() {
if (relations == null)
return null;
Set<Vertex<?>> neighbours = new HashSet<Vertex<?>>();
for (Relation<V,?> r : relations) {
neighbours.add(r.getTarget());
}
return neighbours;
}
/**
* Gets all the immediate neighbors of the given type.
*/
public <T> Set<Vertex<T>> getNeighbours(Class<T> type, boolean allowSubclass) {
if (relations == null)
return null;
Set<Vertex<T>> neighbours = new HashSet<Vertex<T>>();
for (Relation<V,?> r : relations) {
Vertex<?> target = r.getTarget();
boolean include = allowSubclass ? type.isAssignableFrom(target.getClass()) : type == target.getClass();
if (include)
neighbours.add((Vertex<T>)target);
}
return neighbours;
}
}