From a79a96b86c79d9a7105d6c439a641888572f6284 Mon Sep 17 00:00:00 2001 From: Pinaki Poddar Date: Wed, 16 Jun 2010 23:22:31 +0000 Subject: [PATCH] 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 --- .../persistence/graph/AbstractGraph.java | 47 +++++ .../openjpa/persistence/graph/City.java | 6 +- .../openjpa/persistence/graph/Graph.java | 109 ++++++++++ .../openjpa/persistence/graph/People.java | 7 +- .../persistence/graph/PersistentGraph.java | 53 +++++ .../persistence/graph/PersistentRelation.java | 193 ++++++++++++++++++ .../openjpa/persistence/graph/Relation.java | 165 ++++----------- .../persistence/graph/RelationGraph.java | 163 +++++++++++++++ .../graph/TestPersistentGraph.java | 110 +++++----- .../openjpa/persistence/graph/Vertex.java | 155 -------------- 10 files changed, 667 insertions(+), 341 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/AbstractGraph.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Graph.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentGraph.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentRelation.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/RelationGraph.java delete mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/AbstractGraph.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/AbstractGraph.java new file mode 100644 index 000000000..400a544fe --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/AbstractGraph.java @@ -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 type of element of the graph. + */ +public abstract class AbstractGraph extends AbstractSet implements Graph { + /** + * 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> rs = getRelationsTo(node); + for (Relation r : rs) { + delink(r.getSource(), node); + } + return super.remove(e); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java index 7f5f82ae5..559311c3d 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java @@ -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 { +public class City implements Serializable { @Id private String name; diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Graph.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Graph.java new file mode 100644 index 000000000..ba952422a --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Graph.java @@ -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}. + *
+ * 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 Type of element of the graph + * + * + * @author Pinaki Poddar + * + */ +public interface Graph extends Set { + /** + * 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 generic type of source node + * @param generic type of target node + * + * @return a relation + */ + public Relation 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 generic type of source node + * @param generic type of target node + * + * @return the existing relation, if any, that had been broken. null otherwise. + */ + public Relation 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 generic type of source node + * @param generic type of target node + * + * @return a relation between the nodes, if exists. null otherwise. + */ + public Relation 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 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 getSources(E target); + + /** + * Gets all the relations originating from the given source. + * @param + * @param source + * @return + */ + public Set> getRelationsFrom(V source); + + /** + * Gets all the relations terminating on the given target. + * @param + * @param target + * @return + */ + public Set> getRelationsTo(V target); +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java index db2c3a32d..7628dfab4 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java @@ -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{ +public class People implements Serializable { @Id private long ssn; private String name; + public long getSsn() { return ssn; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentGraph.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentGraph.java new file mode 100644 index 000000000..d19e2f2ae --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentGraph.java @@ -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}. + *
+ * 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 not represent + * container instances such as {@link java.util.Set} or {@link java.util.List} as first class 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. + *
+ * Persistent Graph defines a auto-generated persistent identity. + * + * @author Pinaki Poddar + * + * @param type of element. + */ +@SuppressWarnings("serial") +@MappedSuperclass +public abstract class PersistentGraph extends AbstractGraph implements Serializable { + + @Id + @GeneratedValue + private long id; + + public final long getId() { + return id; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentRelation.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentRelation.java new file mode 100644 index 000000000..4744103bb --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/PersistentRelation.java @@ -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. + *
+ * A relation is + *
    + *
  1. generic because Relation type is parameterized with the type of vertices it links. + *
  2. directed because it distinguishes the two vertices as source and target. + *
  3. attributed because any arbitrary name-value pair can be associated with a relation. + *
+ * A relation is made persistence capable by annotating its generic source and target as persistent entity. + * A relation is also a first-class entity having its own persistent identifier. + *
+ * A relation is immutable in terms of its two vertices. The properties + * attached to a relation, however, can change. + *
+ * @param the type of source vertex linked by this relation. + * @param the type of target vertex linked by this relation. + * + * @author Pinaki Poddar + * + */ +@SuppressWarnings("serial") +@Entity +public class PersistentRelation implements Relation, 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 + * java.util.Properties. + *
+ * Declaring the key-value pairs as java.util.Properties makes OpenJPA + * assume that both key and value will be stored in database as String. + * This is not strictly correct because java.util.Properties + * declares its key and value as java.lang.Object. 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. + *
+ * Another possibility to express key-value pair as + *
+ * Map attrs; + *
+ * 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. + *
+ * The third alternative is a Map where keys are String and values are Object + *
+ * Map attrs; + * 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 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 removeAttribute(String attr) { + attrs.remove(attr); + return this; + } + + public String toString() { + return source + "->" + target; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java index 24602ca42..e8b5b1f87 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java @@ -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. *
* A relation is *
    - *
  1. generic because the vertices it links are generically typed. - *
  2. directed because it distinguishes the two end points as source and target. - *
  3. attributed because any arbitrary name-value pair can be associated with a relation. + *
  4. generic because Relation type is parameterized with the type of vertices it links. + *
  5. directed because it distinguishes the two vertices as source and target. + *
  6. attributed because any arbitrary key-value pair can be associated with a relation. *
+ *
* 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. + *
* @param the type of source vertex linked by this relation. * @param the type of target vertex linked by this relation. * * @author Pinaki Poddar * */ -@SuppressWarnings("serial") -@Entity -public class Relation 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 source; - - /** - * A Relation must have a non-null vertex as source. - */ - @OneToOne(optional=false) - private Vertex target; - - /** - * The properties of a Relation is a set of key-value pairs and is declared as - * java.util.Properties. - *
- * Declaring the key-value pairs as java.util.Properties makes OpenJPA - * assume that both key and value will be stored in database as String. - * This is not strictly correct because java.util.Properties - * declares its key and value as java.lang.Object. 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. - *
- * Another possibility to express key-value pair as - *
- * Map attrs; - *
- * 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. - *
- * The third alternative is a Map where keys are String and values are Object - *
- * Map attrs; - * 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 s, Vertex 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 { /** * Gets the immutable source vertex. - */ - public Vertex getSource() { - return source; - } + * + * @return a non-null source vertex. + */ + public V1 getSource(); /** * Gets the immutable target vertex. - */ - public Vertex 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 fluent method-chaining */ - public boolean hasAttribute(String attr) { - return attrs.containsKey(attr); - } + public Relation 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 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 fluent method chaining. */ - public Relation removeAttribute(String attr) { - attrs.remove(attr); - return this; - } + public Relation removeAttribute(String key); + + /** + * Gets the key-value pairs associated with this relation. + */ + public Properties getAttributes(); } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/RelationGraph.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/RelationGraph.java new file mode 100644 index 000000000..5fd7db58c --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/RelationGraph.java @@ -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 extends PersistentGraph { + @OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}) + private Set> relations = new HashSet>(); + + /* + * 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 Relation 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 r = getRelation(source, target); + if (r == null) { + r = new PersistentRelation(source, target); + relations.add((PersistentRelation) r); + } + return r; + + } + + /* + * Delinks the given vertices, if they are currently connected. + * + * + * @see org.apache.openjpa.persistence.graph.Graph#delink(V1, V2) + */ + public Relation delink(V1 source, V2 target) { + Relation 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 Relation getRelation(V1 source, V2 target) { + for (Relation r : relations) { + if (r.getSource().equals(source) && r.getTarget() != null && r.getTarget().equals(target)) { + return (Relation)r; + } + } + return null; + } + + /** + * Iterator over the nodes of this graph. + */ + public Iterator iterator() { + return getNodes().iterator(); + } + + public int size() { + return 0; + } + + @Override + public Set> getRelationsFrom(V source) { + Set> rs = new HashSet>(); + for (Relation r : relations) { + if (r.getSource().equals(source) && r.getTarget() != null) + rs.add((Relation)r); + } + return rs; + } + + @Override + public Set> getRelationsTo(V target) { + Set> rs = new HashSet>(); + for (Relation r : relations) { + if (r.getTarget() != null && r.getTarget().equals(target)) + rs.add((Relation)r); + } + return rs; + } + + @Override + public Set getSources(Object target) { + Set sources = new HashSet(); + for (Relation r : relations) { + if (r.getTarget() != null && r.getTarget().equals(target)) + sources.add(r.getSource()); + } + return sources; + } + + @Override + public Set getTargets(Object source) { + Set targets = new HashSet(); + for (Relation r : relations) { + if (r.getSource().equals(source) && r.getTarget() != null) + targets.add(r.getTarget()); + } + return targets; + } + + public Set 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,null)); + return true; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java index f57667732..2d24aa92d 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java @@ -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 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 strategy, Class 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 relations = em.createQuery("select r from Relation r", Relation.class).getResultList(); + String jpql = "select r from PersistentRelation r"; + List 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 result = em.createQuery(jpql, Relation.class) + String jpql = "select r from PersistentRelation r where r.source = :node"; + List 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 result = em.createQuery(jpql, Relation.class) + String jpql = "select r from PersistentRelation r join r.attrs a where key(a) = :key"; + List 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 result = em.createQuery(jpql, Relation.class) + List 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 result = em.createQuery(jpql, Relation.class) + List 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 result2 = em.createQuery(wrongJPQL, Relation.class) + List 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 r = em.createQuery(jpql, Relation.class) + String jpql = "select r from PersistentRelation r where r.source = :node"; + List 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 createData() { + PersistentGraph graph = new RelationGraph(); + 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 r = people[i].link(people[j]).addAttribute(ATTR_EMOTION, EMOTIONS[i][j]); + Relation 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 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 r = p1.getRelationTo(p2); + Relation 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 r12 = c1.getRelationTo(c2); + Relation 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 r = p.getRelationTo(c); + Relation r = graph.getRelation(p,c); if (i % CITY_NAMES.length == j) { assertNotNull(r); } else { diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java deleted file mode 100644 index c1077f5e0..000000000 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java +++ /dev/null @@ -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. - *
- * A vertex maintains relationship to other connected vertices. - * An abstract vertex provides the functionality for a concrete derivation to be member of a graph. - *
- * A generic vertex does not define a persistent identity on the derived types. - *
- * The only persistent state maintained by a generic vertex is its ({@linkplain Relation relations} - * to its neighboring vertices. - *
- * @author Pinaki Poddar - * - * @param the type of this vertex. - */ -@SuppressWarnings("serial") - -@MappedSuperclass -public abstract class Vertex 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> 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 Relation link(Vertex n) { - if (n == null) { - throw new NullPointerException(this + " can not link to null target"); - } - Relation r = getRelationTo(n); - if (r == null) { - r = new Relation(this, n); - if (relations == null) { - relations = new HashSet>(); - } - 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 Relation delink(Vertex n) { - Relation 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 Relation getRelationTo(Vertex n) { - if (n == null || relations == null) - return null; - for (Relation r : relations) { - if (r.getTarget().equals(n)) { - return (Relation)r; - } - } - return null; - } - - /** - * Affirms if the given vertex is linked from this vertex. - */ - public boolean isRelatedTo(Vertex n) { - return getRelationTo(n) != null; - } - - /** - * Gets all the relations starting from this vertex. - */ - public Set> getRelations() { - return relations; - } - - /** - * Gets all the immediate neighbors. - */ - public Set> getNeighbours() { - if (relations == null) - return null; - Set> neighbours = new HashSet>(); - for (Relation r : relations) { - neighbours.add(r.getTarget()); - } - return neighbours; - } - - /** - * Gets all the immediate neighbors of the given type. - */ - public Set> getNeighbours(Class type, boolean allowSubclass) { - if (relations == null) - return null; - Set> neighbours = new HashSet>(); - for (Relation r : relations) { - Vertex target = r.getTarget(); - boolean include = allowSubclass ? type.isAssignableFrom(target.getClass()) : type == target.getClass(); - if (include) - neighbours.add((Vertex)target); - } - return neighbours; - } - -}