openjpa-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ppod...@apache.org
Subject svn commit: r952893 - in /openjpa/trunk: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/ openjpa-kernel/src/main/java/org/a...
Date Wed, 09 Jun 2010 02:43:47 GMT
Author: ppoddar
Date: Wed Jun  9 02:43:46 2010
New Revision: 952893

URL: http://svn.apache.org/viewvc?rev=952893&view=rev
Log:
OPENJPA-1686: A first draft of support for generic graph. Correct mapping strategy on MappedSuperClass (they are not Embeddables)

Added:
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java   (with props)
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java   (with props)
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java   (with props)
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java   (with props)
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java   (with props)
Modified:
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java
    openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/Id.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/OpenJPAId.java
    openjpa/trunk/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/JavaVersions.java
    openjpa/trunk/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestEmbeddableSuperclass.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/annotations/TestEmbeddableSuperclass.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java Wed Jun  9 02:43:46 2010
@@ -1026,7 +1026,7 @@ public class MappingRepository extends M
             //bi-/M-1/JoinTable ==> join table strategy
             if (isBiMTo1JT(field)) 
                 return false;
-            if (mapped.getTypeCode() == JavaTypes.PC)
+            if (mapped.getTypeCode() == JavaTypes.PC || mapped.getTypeCode() == JavaTypes.PC_UNTYPED)
                 return true;
             if (mapped.getElement().getTypeCode() == JavaTypes.PC)
                 return false;

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/RelationToManyInverseKeyFieldStrategy.java Wed Jun  9 02:43:46 2010
@@ -133,7 +133,8 @@ public abstract class RelationToManyInve
         boolean criteria = vinfo.getUseClassCriteria();
         if (mapped != null) {
             mapped.resolve(mapped.MODE_META | mapped.MODE_MAPPING);
-            if (!(mapped.getStrategy() instanceof RelationFieldStrategy))
+            if (!(mapped.getStrategy() instanceof RelationFieldStrategy 
+               || mapped.getHandler() instanceof UntypedPCValueHandler))
                 throw new MetaDataException(_loc.get("not-inv-relation",
                     field, mapped));
             vinfo.assertNoSchemaComponents(elem, !adapt);

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/StoreCollectionFieldStrategy.java Wed Jun  9 02:43:46 2010
@@ -31,6 +31,7 @@ import org.apache.openjpa.jdbc.kernel.JD
 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.RelationId;
 import org.apache.openjpa.jdbc.meta.ValueMapping;
 import org.apache.openjpa.jdbc.schema.Column;
 import org.apache.openjpa.jdbc.schema.ForeignKey;
@@ -46,6 +47,7 @@ import org.apache.openjpa.meta.ClassMeta
 import org.apache.openjpa.meta.JavaTypes;
 import org.apache.openjpa.util.ChangeTracker;
 import org.apache.openjpa.util.Id;
+import org.apache.openjpa.util.OpenJPAId;
 import org.apache.openjpa.util.Proxy;
 
 /**
@@ -576,8 +578,9 @@ public abstract class StoreCollectionFie
     protected Joins selectAll(Select sel, ClassMapping elem,
         OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch,
         int eagerMode) {
-        sel.whereForeignKey(getJoinForeignKey(elem), sm.getObjectId(),
-            field.getDefiningMapping(), store);
+        ForeignKey fk = getJoinForeignKey(elem);
+        Object oid = getObjectIdForJoin(fk, sm);
+        sel.whereForeignKey(fk, oid, field.getDefiningMapping(), store);
 
         // order first, then select so that if the projection introduces
         // additional ordering, it will be after our required ordering
@@ -601,4 +604,26 @@ public abstract class StoreCollectionFie
     boolean requiresOrderBy() {
     	return List.class.isAssignableFrom(field.getProxyType());
     }
+    
+    /**
+     * Gets the identity value of the given instance that is suitable to join to the given foreign key.
+     * The special case of the foreign key being a relation identifier will encode the value. 
+     */
+    Object getObjectIdForJoin(ForeignKey fk, OpenJPAStateManager sm) {
+        Object oid = sm.getObjectId();
+        for (Column col : fk.getColumns()) {
+            if (!col.isRelationId()) {
+                return oid;
+            }
+        }
+        
+        FieldMapping owningField = field.getMappedByMapping();
+        if (owningField != null && owningField.getHandler() instanceof RelationId) {
+            return ((RelationId)owningField.getHandler()).toRelationDataStoreValue(sm, null);
+        } 
+        if (oid instanceof OpenJPAId) {
+            return ((OpenJPAId)oid).getIdObject();
+        }
+        return oid;
+    }
 }

Modified: openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java (original)
+++ openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SelectImpl.java Wed Jun  9 02:43:46 2010
@@ -1343,6 +1343,7 @@ public class SelectImpl
     private void where(Object oid, ClassMapping mapping, Column[] toCols,
         Column[] fromCols, Object[] vals, Column[] constCols, PathJoins pj,
         JDBCStore store) {
+        boolean relationId = fromCols[0].isRelationId(); 
         ValueMapping embed = mapping.getEmbeddingMapping();
         if (embed != null) {
             where(oid, embed.getFieldMapping().getDefiningMapping(),
@@ -1352,7 +1353,7 @@ public class SelectImpl
 
         // only bother to pack pk values into array if app id
         Object[] pks = null;
-        if (mapping.getIdentityType() == ClassMapping.ID_APPLICATION)
+        if (!relationId && mapping.getIdentityType() == ClassMapping.ID_APPLICATION)
             pks = ApplicationIds.toPKValues(oid, mapping);
 
         SQLBuffer buf = new SQLBuffer(_dict);
@@ -1360,10 +1361,9 @@ public class SelectImpl
         Object val;
         int count = 0;
         for (int i = 0; i < toCols.length; i++, count++) {
-            if (pks == null)
-                val = (oid == null) ? null :
-                        ((Id) oid).getId();
-            else {
+            if (pks == null) {
+                val = (oid == null) ? null : relationId ? oid : ((Id) oid).getId();
+            } else {
                 // must be app identity; use pk index to get correct pk value
                 join = mapping.assertJoinable(toCols[i]);
                 val = pks[mapping.getField(join.getFieldIndex()).

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueMetaDataImpl.java Wed Jun  9 02:43:46 2010
@@ -451,7 +451,7 @@ public class ValueMetaDataImpl
         if (meta != null)
             _decCode = JavaTypes.PC;
         
-        if (meta != null && meta.isEmbeddedOnly())
+        if (meta != null && meta.isEmbeddedOnly() && !meta.isAbstract())
             setEmbedded(true);
                 
         if (!isEmbedded()) 

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/Id.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/Id.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/Id.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/Id.java Wed Jun  9 02:43:46 2010
@@ -39,7 +39,7 @@ public final class Id
 
     /**
      * Create an id from the given type and value; the value might be an
-     * id instnace, a stringified id, or a primary key value.
+     * id instance, a stringified id, or a primary key value.
      */
     public static Id newInstance(Class cls, Object val) {
         if (val instanceof Id)
@@ -77,13 +77,12 @@ public final class Id
      */
     public Id(String str, ClassLoader loader) {
         if (loader == null)
-            loader = AccessController.doPrivileged(
-                J2DoPrivHelper.getContextClassLoaderAction());
+            loader = AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
 
         if (str == null)
             _id = 0L;
         else {
-            int dash = str.indexOf('-');
+            int dash = str.indexOf(TYPE_VALUE_SEP);
             try {
                 type = Class.forName(str.substring(0, dash), true, loader);
             } catch (Throwable t) {
@@ -104,7 +103,7 @@ public final class Id
             _id = 0L;
         else {
             // allow either stringified long or result of Id.toString
-            int dash = key.indexOf('-');
+            int dash = key.indexOf(TYPE_VALUE_SEP);
             if (dash > 0) // don't check for -1; might be negative number
                 key = key.substring(dash + 1);
             _id = Long.parseLong(key);

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/OpenJPAId.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/OpenJPAId.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/OpenJPAId.java (original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/util/OpenJPAId.java Wed Jun  9 02:43:46 2010
@@ -24,21 +24,23 @@ import org.apache.openjpa.lib.util.Refer
 import org.apache.openjpa.lib.util.concurrent.ConcurrentReferenceHashMap;
 
 /**
- * Identity class extended by builtin OpenJPA identity objects.
+ * Identity class extended by built-in OpenJPA identity objects.
  *
  * @author Steve Kim
  */
+@SuppressWarnings("serial")
 public abstract class OpenJPAId
     implements Comparable, Serializable {
-
-    // cache the types' generated hashcodes
+    public static final char TYPE_VALUE_SEP = '-';
+    
+    // cache the types' generated hash codes
     private static ConcurrentReferenceHashMap _typeCache =
         new ConcurrentReferenceHashMap(ReferenceMap.WEAK, ReferenceMap.HARD);
 
     protected Class type;
     protected boolean subs = true;
 
-    // type has his based on the least-derived non-object class so that
+    // type hash is based on the least-derived non-object class so that
     // user-given ids with non-exact types match ids with exact types
     private transient int _typeHash = 0;
 
@@ -100,7 +102,7 @@ public abstract class OpenJPAId
     protected abstract boolean idEquals(OpenJPAId other);
 
     /**
-     * Generate the hashcode for this Id.  Cache the type's generated hashcode
+     * Generate the hash code for this Id.  Cache the type's generated hash code
      * so that it doesn't have to be generated each time.
      */
     public int hashCode() {
@@ -134,7 +136,7 @@ public abstract class OpenJPAId
     }
 
     public String toString() {
-        return type.getName() + "-" + getIdObject();
+        return type.getName() + TYPE_VALUE_SEP + getIdObject();
     }
 
     public int compareTo(Object other) {
@@ -142,7 +144,6 @@ public abstract class OpenJPAId
             return 0;
         if (other == null)
             return 1;
-        return ((Comparable) getIdObject()).compareTo(((OpenJPAId) other).
-            getIdObject ());
+        return ((Comparable) getIdObject()).compareTo(((OpenJPAId) other).getIdObject ());
 	}
 }

Modified: openjpa/trunk/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/JavaVersions.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/JavaVersions.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/JavaVersions.java (original)
+++ openjpa/trunk/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/JavaVersions.java Wed Jun  9 02:43:46 2010
@@ -164,7 +164,7 @@ public class JavaVersions {
         try {
             Object type = Field.class.getMethod("getGenericType",
                 (Class[]) null).invoke(f, (Object[]) null);
-            return collectParameterizedTypes(type);
+            return collectParameterizedTypes(type, f.getType());
         } catch (Exception e) {
             return EMPTY_CLASSES;
         }
@@ -182,7 +182,7 @@ public class JavaVersions {
         try {
             Object type = Method.class.getMethod("getGenericReturnType",
                 (Class[]) null).invoke(meth, (Object[]) null);
-            return collectParameterizedTypes(type);
+            return collectParameterizedTypes(type, meth.getReturnType());
         } catch (Exception e) {
             return EMPTY_CLASSES;
         }
@@ -191,10 +191,14 @@ public class JavaVersions {
     /**
      * Return all parameterized classes for the given type.
      */
-    private static Class[] collectParameterizedTypes(Object type)
+    private static Class[] collectParameterizedTypes(Object type, Class<?> cls)
         throws Exception {
-        if (PARAM_TYPE == null || !PARAM_TYPE.isInstance(type))
+        if (PARAM_TYPE == null || !PARAM_TYPE.isInstance(type)) {
+            if (cls.getSuperclass() != Object.class) {
+                return collectParameterizedTypes(cls.getGenericSuperclass(), cls.getSuperclass());
+            }
             return EMPTY_CLASSES;
+        }
 
         Object[] args = (Object[]) PARAM_TYPE.getMethod
             ("getActualTypeArguments", (Class[]) null).invoke(type,

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/main/java/org/apache/openjpa/persistence/jdbc/AnnotationPersistenceMappingParser.java Wed Jun  9 02:43:46 2010
@@ -1052,12 +1052,10 @@ public class AnnotationPersistenceMappin
                     parseEnumerated(fm, (Enumerated) anno);
                     break;
                 case JOIN_COL:
-                    parseJoinColumns(fm, fm.getValueInfo(), true,
-                        (JoinColumn) anno);
+                    parseJoinColumns(fm, fm.getValueInfo(), true, (JoinColumn) anno);
                     break;
                 case JOIN_COLS:
-                    parseJoinColumns(fm, fm.getValueInfo(), true,
-                        ((JoinColumns) anno).value());
+                    parseJoinColumns(fm, fm.getValueInfo(), true, ((JoinColumns) anno).value());
                     break;
                 case JOIN_TABLE:
                     parseJoinTable(fm, (JoinTable) anno);
@@ -1670,12 +1668,19 @@ public class AnnotationPersistenceMappin
         List<Column> cols = new ArrayList<Column>(joins.length);
         int unique = 0;
         DBIdentifier sSecondary = DBIdentifier.NULL;
+        
         for (int i = 0; i < joins.length; i++) {
-            cols.add(newColumn(joins[i]));
+            Column col = newColumn(joins[i]);
+            cols.add(col);
             unique |= (joins[i].unique()) ? TRUE : FALSE;
-            DBIdentifier sTable = DBIdentifier.newTable(joins[i].table(), delimit());
-            sSecondary = trackSecondaryTable(fm, sSecondary,
-                sTable, i);
+            DBIdentifier sTable = DBIdentifier.NULL;
+            if (info instanceof FieldMappingInfo && secondaryAllowed) {
+                sTable = ((FieldMappingInfo)info).getTableIdentifier();
+            }
+            if (sTable.isNull()) {
+                sTable = DBIdentifier.newTable(joins[i].table(), delimit());
+            }
+            sSecondary = trackSecondaryTable(fm, sSecondary, sTable, i);
             if (!secondaryAllowed && !DBIdentifier.isNull(sSecondary))
                 throw new MetaDataException(_loc.get("bad-second", fm));
         }

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestEmbeddableSuperclass.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestEmbeddableSuperclass.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestEmbeddableSuperclass.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/compat/TestEmbeddableSuperclass.java Wed Jun  9 02:43:46 2010
@@ -39,6 +39,7 @@ import org.apache.openjpa.persistence.te
 /**
  * <p>Test embeddable superclasses</p>
  *
+ *  <B>This is an anti-test because it validates or masks root cause of a possibly serious error.
  *
  * <b>Compatible testcases</b> are used to test various backwards compatibility scenarios between JPA 2.0 and JPA 1.2
  * 
@@ -51,6 +52,7 @@ import org.apache.openjpa.persistence.te
  * <ul>
  * <li>The proper openjpa.Compatibility value(s) must be provided in order for the testcase(s) to succeed
  * </ul>
+ * 
  */
 public class TestEmbeddableSuperclass
     extends SingleEMFTestCase {
@@ -67,15 +69,21 @@ public class TestEmbeddableSuperclass
         assertTrue(fm.getStrategy() instanceof RelationFieldStrategy);
 
         fm = cls.getFieldMapping("sup");
-
-        if (OpenJPAVersion.MAJOR_RELEASE >= 2) {
+        assertEquals(RelationFieldStrategy.class, fm.getStrategy().getClass());
+        // This was an anti-test because it legitimizes/masks the root cause of a serious error.
+        // The strategy for a field should not change without a valid reason.
+        
+//        if (OpenJPAVersion.MAJOR_RELEASE >= 2) {
             // OPENJPA-1214 - OpenJPA 2 returns a EmbedFieldStrategy instead of
             // a RelationFieldStrategy as in prior releases.
-            assertTrue(fm.getStrategy() instanceof EmbedFieldStrategy);
-        } else {
+//            assertEquals(EmbedFieldStrategy.class, fm.getStrategy().getClass());
+//        } else {
             // Prior OpenJPA 1.2/1.3 behavior
-            assertTrue(fm.getStrategy() instanceof RelationFieldStrategy);
-        }
+//            assertEquals(RelationFieldStrategy.class, fm.getStrategy().getClass());
+//            
+//        }
+        
+        // 
     } 
 }
 

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java?rev=952893&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java Wed Jun  9 02:43:46 2010
@@ -0,0 +1,46 @@
+/*
+ * 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 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> {
+    @Id
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}

Propchange: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/City.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java?rev=952893&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java Wed Jun  9 02:43:46 2010
@@ -0,0 +1,51 @@
+/*
+ * 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 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>{
+    @Id
+    private long ssn;
+    private String name;
+    public long getSsn() {
+        return ssn;
+    }
+    public void setSsn(long ssn) {
+        this.ssn = ssn;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+
+}

Propchange: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/People.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java?rev=952893&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java Wed Jun  9 02:43:46 2010
@@ -0,0 +1,184 @@
+/*
+ * 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 entity.
+ * <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.
+ * </ol>
+ * A relation is immutable in terms of its two vertices. The properties
+ * attached to a relation can change.
+ *
+ * @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;
+    }
+    
+    /**
+     * Gets the immutable source vertex.
+    */
+    public Vertex<V1> getSource() {
+        return source;
+    }
+    
+    /**
+     * Gets the immutable target vertex.
+    */
+    public Vertex<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;
+    }
+}

Propchange: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Relation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java?rev=952893&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java Wed Jun  9 02:43:46 2010
@@ -0,0 +1,451 @@
+/*
+ * 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.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;
+
+/**
+ * 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;
+
+    public void setUp() throws Exception {
+        super.setUp(CLEAR_TABLES, Vertex.class, Relation.class, People.class, City.class);
+        em = emf.createEntityManager();
+        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);
+    }
+
+    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) OpenJPAPersistence.cast(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 straegy 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.getClass().equals(actualHandler.getClass())) {
+                printMapping(fmd);
+                assertEquals(handler, fmd.getHandler().getClass());
+            }
+        }
+    }
+
+    FieldStrategy getStrategy(Class<?> cls, String field) {
+        MetaDataRepository repos = OpenJPAPersistence.cast(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();
+        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(people, cities);
+
+        em.getTransaction().rollback();
+    }
+
+    /**
+     * Tests that relation can be queried and their references are set
+     * correctly.
+     */
+    public void testQueryRelation() {
+        List<Relation> relations = em.createQuery("select r from Relation r", Relation.class).getResultList();
+        for (Relation<?, ?> r : relations) {
+            Vertex<?> source = r.getSource();
+            Vertex<?> 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 {
+                    fail();
+                }
+            } 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();
+                }
+            }
+        }
+    }
+
+    /**
+     * 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 Relation r where r.source = :node";
+        List<Relation> result = em.createQuery(jpql, Relation.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 Relation r join r.attrs a where key(a) = :key";
+        List<Relation> result = em.createQuery(jpql, Relation.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 Relation 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)
+                                  .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 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 value = EMOTIONS[0][2].toString();
+        List<Relation> result = em.createQuery(jpql, Relation.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 Relation 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)
+                                   .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 Relation r where r.source = :node";
+        List<Relation> r = em.createQuery(jpql, Relation.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 Relation r join r.attrs a where key(a) = :key";
+        Relation newR = em.createQuery(jpql, Relation.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 Relation r join r.attrs a where key(a) = :key";
+        try {
+            newR = em.createQuery(jpql, Relation.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.
+     */
+    void createData() {
+        if (isPopulated())
+            return;
+        em.getTransaction().begin();
+
+        People[] people = new People[SSN.length];
+        for (int i = 0; i < SSN.length; i++) {
+            People p = new People();
+            em.persist(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();
+            em.persist(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]);
+                    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++) {
+                cities[i].link(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]);
+        }
+
+        em.getTransaction().commit();
+    }
+
+    void assertDataEquals(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 = p1.getRelationTo(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 = c1.getRelationTo(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 = p.getRelationTo(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;
+    }
+}

Propchange: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/TestPersistentGraph.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java?rev=952893&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java Wed Jun  9 02:43:46 2010
@@ -0,0 +1,155 @@
+/*
+ * 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;
+    }
+
+}

Propchange: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/graph/Vertex.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/annotations/TestEmbeddableSuperclass.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/annotations/TestEmbeddableSuperclass.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/annotations/TestEmbeddableSuperclass.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/annotations/TestEmbeddableSuperclass.java Wed Jun  9 02:43:46 2010
@@ -107,7 +107,7 @@ public class TestEmbeddableSuperclass
         assertTrue(fm.getStrategy() instanceof RelationFieldStrategy);
 
         fm = cls.getFieldMapping("sup");
-        assertTrue(fm.getStrategy() instanceof EmbedFieldStrategy);
+        assertEquals(RelationFieldStrategy.class, fm.getStrategy().getClass());
     }
 
     public void testPersistAndFind() {

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java (original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java Wed Jun  9 02:43:46 2010
@@ -85,6 +85,7 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.util.Stack;
 import java.util.TreeSet;
@@ -1285,7 +1286,7 @@ public class AnnotationPersistenceMetaDa
     /**
      * Convert the given class to its OpenJPA type override equivalent.
      */
-    private static Class<?> toOverrideType(Class<?> cls) {
+    public static Class<?> toOverrideType(Class<?> cls) {
         return (cls == Entity.class)
             ? org.apache.openjpa.enhance.PersistenceCapable.class : cls;
     }
@@ -1506,12 +1507,12 @@ public class AnnotationPersistenceMetaDa
             case JavaTypes.ARRAY:
             case JavaTypes.COLLECTION:
             case JavaTypes.MAP:
-                if (JavaTypes.maybePC(fmd.getElement()))
+                if (fmd.getDeclaredType() == Properties.class || JavaTypes.maybePC(fmd.getElement()))
                     break;
                 // no break
             default:
                 throw new MetaDataException(_loc.get("bad-meta-anno", fmd,
-                    "OneToMany"));
+                    "ManyToMany"));
         }
 
         fmd.setInDefaultFetchGroup(anno.fetch() == FetchType.EAGER);

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java?rev=952893&r1=952892&r2=952893&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java (original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/XMLPersistenceMetaDataParser.java Wed Jun  9 02:43:46 2010
@@ -33,6 +33,7 @@ import java.util.Set;
 import java.util.Stack;
 
 import javax.persistence.CascadeType;
+import javax.persistence.Entity;
 import javax.persistence.GenerationType;
 import javax.persistence.LockModeType;
 import javax.persistence.NamedQuery;
@@ -1467,7 +1468,7 @@ public class XMLPersistenceMetaDataParse
         }
         val = attrs.getValue("target-entity");
         if (val != null)
-            fmd.setTypeOverride(classForName(val));
+            fmd.setTypeOverride(AnnotationPersistenceMetaDataParser.toOverrideType(classForName(val)));
         assertPC(fmd, "OneToOne");
         fmd.setSerialized(false); // override any Lob annotation
         boolean orphanRemoval = Boolean.valueOf(attrs.getValue(
@@ -1489,7 +1490,7 @@ public class XMLPersistenceMetaDataParse
         }
         val = attrs.getValue("target-entity");
         if (val != null)
-            fmd.setTypeOverride(classForName(val));
+            fmd.setTypeOverride(AnnotationPersistenceMetaDataParser.toOverrideType(classForName(val)));
         assertPC(fmd, "ManyToOne");
         fmd.setSerialized(false); // override any Lob annotation
         String mapsId = attrs.getValue("maps-id");



Mime
View raw message