You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jdo-commits@db.apache.org by mb...@apache.org on 2021/05/20 18:55:48 UTC

[db-jdo] branch master updated: Jdo 709 (#20)

This is an automated email from the ASF dual-hosted git repository.

mbo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/db-jdo.git


The following commit(s) were added to refs/heads/master by this push:
     new 7913371  Jdo 709 (#20)
7913371 is described below

commit 7913371a10da6e9bc7d33079ace023f0d37897d4
Author: Michael Bouschen <mb...@apache.org>
AuthorDate: Thu May 20 20:55:39 2021 +0200

    Jdo 709 (#20)
    
    * JDO-709: First TCK test case for attribute converters
    
    * JDO-709: Added Query Test case
    
    * JDO-709: Renamed PCRect2 to PCRectString, removed debug logging
    
    * JDO-709: Apply suggestions from code review - fixed typos
    
    Co-authored-by: Tobias Bouschen <to...@googlemail.com>
    
    * JDO-709: Apply suggestions from code review - synchronized method and removed code in comment
    
    Co-authored-by: Tobias Bouschen <to...@googlemail.com>
    
    * JDO-709: Apply suggestions from code review - use SingleFieldIdentity for PCRectString class
    
    Co-authored-by: Tilmann <ti...@apache.org>
    
    * JDO-709: Apply suggestions from code review - fixed typo
    
    Co-authored-by: Tobias Bouschen <to...@googlemail.com>
    
    Co-authored-by: Tobias Bouschen <to...@googlemail.com>
    
    * JDO-709: Improved AttributeConverter Test
    
    - Added separate test cases for storing and reading a PCRectString instance
    - Added new test case for modifying a  PCRectString instance
    - AttributeConverter implementation moved from static nested class  of PCRectString to util package
    - Added helper method to create PCRectString instances to TCK test class
    - Removed author tag
    
    * JDO-709: Added query test case using string parameter
    
    * JDO-709: Added PC class using annotations to define the converter class
    
    * JDO-709: update to latest DN
    
    Co-authored-by: Tobias Bouschen <to...@googlemail.com>
    Co-authored-by: Tilmann <ti...@apache.org>
---
 tck/pom.xml                                        |  20 +-
 .../tck/api/converter/AttributeConverterTest.java  | 375 +++++++++++++++++++++
 .../java/org/apache/jdo/tck/pc/mylib/IPCRect.java  |  28 ++
 .../org/apache/jdo/tck/pc/mylib/PCRectString.java  |  70 ++++
 .../jdo/tck/pc/mylib/PCRectStringAnnotated.java    |  86 +++++
 .../java/org/apache/jdo/tck/pc/mylib/Point.java    |   7 +-
 .../jdo/tck/util/PointToStringConverter.java       | 100 ++++++
 tck/src/main/resources/conf/configurations.list    |   1 +
 tck/src/main/resources/conf/converter.conf         |  23 ++
 .../org/apache/jdo/tck/pc/mylib/package.jdo        |  12 +
 .../org/apache/jdo/tck/pc/mylib/package.jdo        |   7 +
 .../apache/jdo/tck/pc/mylib/package-standard.orm   |   6 +
 .../apache/jdo/tck/pc/mylib/package-standard.orm   |  11 +
 .../sql/derby/applicationidentity/schema.sql       |  16 +
 .../sql/derby/datastoreidentity/schema.sql         |  18 +
 15 files changed, 760 insertions(+), 20 deletions(-)

diff --git a/tck/pom.xml b/tck/pom.xml
index cb1ca6d..0dfa0e2 100644
--- a/tck/pom.xml
+++ b/tck/pom.xml
@@ -193,17 +193,17 @@
                 <dependency>
                     <groupId>org.datanucleus</groupId>
                     <artifactId>datanucleus-core</artifactId>
-                    <version>5.2.4</version>
+                    <version>5.2.7</version>
                 </dependency>
                 <dependency>
                     <groupId>org.datanucleus</groupId>
                     <artifactId>datanucleus-rdbms</artifactId>
-                    <version>5.2.4</version>
+                    <version>5.2.7</version>
                 </dependency>
                 <dependency>
                     <groupId>org.datanucleus</groupId>
                     <artifactId>datanucleus-api-jdo</artifactId>
-                    <version>5.2.4</version>
+                    <version>5.2.6</version>
                 </dependency>
                 <dependency>
                     <groupId>org.datanucleus</groupId>
@@ -213,7 +213,7 @@
                 <dependency>
                     <groupId>org.datanucleus</groupId>
                     <artifactId>datanucleus-api-jpa</artifactId>
-                    <version>5.2.5</version>
+                    <version>5.2.6</version>
                 </dependency>
                 <dependency>
                     <groupId>org.apache.logging.log4j</groupId>
@@ -243,18 +243,6 @@
                     <version>4.2.2</version>
                 </dependency>
             </dependencies>
-            <repositories>
-                <repository>
-                    <releases>
-                        <enabled>false</enabled>
-                    </releases>
-                    <snapshots>
-                        <enabled>true</enabled>
-                    </snapshots>
-                    <id>DataNucleus Nightly</id>
-                    <url>http://www.datanucleus.org/downloads/maven2-nightly</url>
-                </repository>
-            </repositories>
         </profile>
     </profiles>
 
diff --git a/tck/src/main/java/org/apache/jdo/tck/api/converter/AttributeConverterTest.java b/tck/src/main/java/org/apache/jdo/tck/api/converter/AttributeConverterTest.java
new file mode 100644
index 0000000..36f1b7b
--- /dev/null
+++ b/tck/src/main/java/org/apache/jdo/tck/api/converter/AttributeConverterTest.java
@@ -0,0 +1,375 @@
+/*
+ * 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.jdo.tck.api.converter;
+
+import org.apache.jdo.tck.JDO_Test;
+import org.apache.jdo.tck.pc.mylib.IPCRect;
+import org.apache.jdo.tck.pc.mylib.PCRectString;
+import org.apache.jdo.tck.pc.mylib.PCRectStringAnnotated;
+import org.apache.jdo.tck.pc.mylib.Point;
+import org.apache.jdo.tck.util.BatchTestRunner;
+import org.apache.jdo.tck.util.PointToStringConverter;
+
+import javax.jdo.JDOHelper;
+import javax.jdo.Query;
+import javax.jdo.Transaction;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ *<B>Title:</B>AttributeConverterTest
+ *<BR>
+ *<B>Keywords:</B> mapping
+ *<BR>
+ *<B>Assertion ID:</B> [not identified]
+ *<BR>
+ *<B>Assertion Description: </B>
+ * A IPCRect instance refers two Point instances, that are stored as strings in the datastore.
+ * A Point instance is converted using an AttributeConverter.
+ */
+public class AttributeConverterTest extends JDO_Test {
+
+    private static final int UL_X = 1;
+    private static final int UL_Y = 10;
+    private static final int LR_X = 10;
+    private static final int LR_Y = 1;
+
+    /**
+     * The <code>main</code> is called when the class
+     * is directly executed from the command line.
+     * @param args The arguments passed to the program.
+     */
+    public static void main(String[] args) {
+        BatchTestRunner.run(AttributeConverterTest.class);
+    }
+
+    /**
+     * @see org.apache.jdo.tck.JDO_Test#localSetUp()
+     */
+    @Override
+    protected void localSetUp() {
+        addTearDownClass(PCRectString.class);
+        addTearDownClass(PCRectStringAnnotated.class);
+    }
+
+    /**
+     * Test method creating and storing a PCRectString instance.
+     */
+    public void testStorePCRectStringInstance() {
+        runStoreIPCRectInstance(PCRectString.class);
+    }
+
+    /**
+     * Test method reading a PCRectString instance from the datastore.
+     */
+    public void testReadPCRectStringInstance() {
+        runReadIPCRectInstance(PCRectString.class);
+    }
+
+    /**
+     * Test method modifying a PCRectString instance and storing in the datastore.
+     */
+    public void testModifyPCRectStringInstance() {
+        runModifyIPCRectInstance(PCRectString.class);
+    }
+
+    /**
+     * Test method running a PCRectString query with a query parameter of type Point.
+     */
+    public void testPCRectStringQueryWithPointParam() throws Exception {
+        runQueryWithPointParameter(PCRectString.class);
+    }
+
+    /**
+     * Test method running a PCRectString query with a query parameter of type String.
+     */
+    public void testPCRectStringQueryWithStringParam() throws Exception {
+        runQueryWithStringParameter(PCRectString.class);
+    }
+
+    /**
+     * Test method creating and storing a PCRectStringAnnotated instance.
+     */
+    public void testStorePCRectStringAnnotatedInstance() {
+        runStoreIPCRectInstance(PCRectStringAnnotated.class);
+    }
+
+    /**
+     * Test method reading a PCRectStringAnnotated instance from the datastore.
+     */
+    public void testReadPCRectStringAnnotatedInstance() {
+        runReadIPCRectInstance(PCRectStringAnnotated.class);
+    }
+
+    /**
+     * Test method modifying a PCRectStringAnnotated instance and storing in the datastore.
+     */
+    public void testModifyPCRectStringAnnotatedInstance() {
+        runModifyIPCRectInstance(PCRectStringAnnotated.class);
+    }
+
+    /**
+     * Test method running a PCRectStringAnnotated query with a query parameter of type String.
+     */
+    public void testPCRectStringAnnotatedQueryWithPointParam() throws Exception {
+        runQueryWithPointParameter(PCRectStringAnnotated.class);
+    }
+
+    /**
+     * Test method running a PCRectStringAnnotated query with a query parameter of type Point.
+     */
+    public void testPCRectStringAnnotatedQueryWithStringParam() throws Exception {
+        runQueryWithStringParameter(PCRectStringAnnotated.class);
+    }
+
+    // Helper methods
+
+    /**
+     * Helper method creating a IPCRect instance.
+     * It should call AttributeConverter method convertToDatastore.
+     */
+    private <T extends IPCRect> void runStoreIPCRectInstance(Class<T> pcrectClass) {
+        int nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        int nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+
+        // Create a persistent IPCRect instance and store its oid
+        // AttributeConverter method convertToDatastore is called when persisting instance
+        createIPCRectInstances(pcrectClass, 1);
+
+        // convertToDatastore should be called twice
+        assertEquals(2, PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls);
+        // convertToAttribute should not be called
+        assertEquals(0, PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls);
+    }
+
+    /**
+     * Helper method reading a IPCRect instance from the datastore.
+     * It should call AttributeConverter method convertToAttribute.
+     */
+    private <T extends IPCRect> void runReadIPCRectInstance(Class<T> pcrectClass) {
+        IPCRect rect;
+        Object oid;
+        int nrOfDbCalls;
+        int nrOfAttrCalls;
+
+        // Create a persistent IPCRect instance and store its oid
+        oid = createIPCRectInstances(pcrectClass, 1);
+
+        // Cleanup the 2nd-level cache and close the pm to make sure PCRect instances are not cached
+        pm.getPersistenceManagerFactory().getDataStoreCache().evictAll(false, pcrectClass);
+        pm.close();
+        pm = null;
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        pm = getPM();
+        pm.currentTransaction().begin();
+        // Read the IPCRect instance from the datastore, this should call convertToAttribute
+        rect = (IPCRect)pm.getObjectById(oid);
+        Point ul = rect.getUpperLeft();
+        Point lr = rect.getLowerRight();
+        pm.currentTransaction().commit();
+
+        // convertToDatastore should not be called
+        assertEquals(0, PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls);
+        // convertToAttribute should be called twice
+        assertEquals(2, PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls);
+        // Check the values of the associated Point instances
+        assertEquals(UL_X, ul.getX());
+        assertEquals(UL_Y, ul.getY() == null ? 0 : ul.getY().intValue());
+        assertEquals(LR_X, lr.getX());
+        assertEquals(LR_Y, lr.getY() == null ? 0 : lr.getY().intValue());
+    }
+
+    /**
+     * Helper method modifying a IPCRect instance.
+     * It should call AttributeConverter method convertToDatastore.
+     */
+    private <T extends IPCRect> void runModifyIPCRectInstance(Class<T> pcrectClass) {
+        Transaction tx;
+        IPCRect rect;
+        Object oid;
+        int nrOfDbCalls;
+        int nrOfAttrCalls;
+
+        // Create a persistent IPCRect instance and store its oid
+        oid = createIPCRectInstances(pcrectClass, 1);
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        pm = getPM();
+        tx = pm.currentTransaction();
+        tx.begin();
+        rect = (IPCRect)pm.getObjectById(oid);
+        // Update IPCRect instance, this should call convertToDatastore
+        rect.setUpperLeft(new Point(UL_X + 1, UL_Y + 1));
+        rect.setLowerRight(new Point(LR_X + 1, LR_Y + 1));
+        // IPCRect instance should be dirty
+        assertTrue(JDOHelper.isDirty(rect));
+        tx.commit();
+
+        // convertToDatastore should be called twice
+        assertEquals(2, PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls);
+        // convertToAttribute should not be called
+        assertEquals(0, PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls);
+    }
+
+    /**
+     * Helper method running a query with a Point parameter.
+     * The parameter value is converted using the AttributeConverter.
+     * @throws Exception
+     */
+    private <T extends IPCRect> void runQueryWithPointParameter(Class<T> pcrectClass) throws Exception {
+        int nrOfDbCalls;
+        int nrOfAttrCalls;
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        createIPCRectInstances(pcrectClass, 5);
+        // convertToDatastore should be called twice per instance = 10 times
+        assertEquals(10, PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls);
+        // convertToAttribute should not be called
+        assertEquals(0, PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls);
+
+        // Cleanup the 2nd-level cache and close the pm to make sure PCRect instances are not cached
+        pm.getPersistenceManagerFactory().getDataStoreCache().evictAll(false, pcrectClass);
+        pm.close();
+        pm = null;
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        pm = getPM();
+        pm.currentTransaction().begin();
+        try (Query<T> q = pm.newQuery(pcrectClass, "this.upperLeft == :point")) {
+            q.setParameters(new Point(UL_X + 1, UL_Y + 1));
+            // AttributeConverter method convertToAttribute is called when loading instance from the datastore
+            List<T> res = q.executeList();
+            assertEquals(1, res.size());
+            IPCRect rect = res.get(0);
+            Point ul = rect.getUpperLeft();
+            Point lr = rect.getLowerRight();
+
+            // Check the coordinates of the associated Point instances
+            assertEquals(UL_X+1, ul.getX());
+            assertEquals(UL_Y+1, ul.getY() == null ? 0 : ul.getY().intValue());
+            assertEquals(LR_X+1, lr.getX());
+            assertEquals(LR_Y+1, lr.getY() == null ? 0 : lr.getY().intValue());
+        } catch (Exception e) {
+            fail(e.getMessage());
+        } finally {
+            pm.currentTransaction().commit();
+        }
+
+        // convertToDatastore should be called to handle the query parameter
+        assertTrue(PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls >= 1);
+        // convertToAttribute should be called at least twice
+        assertTrue(PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls >= 2);
+    }
+
+    /**
+     * Helper method running a query with a Point parameter.
+     * The string parameter is compared to the converted Point field.
+     * @throws Exception
+     */
+    private <T extends IPCRect> void runQueryWithStringParameter(Class<T> pcrectClass) throws Exception {
+        int nrOfDbCalls;
+        int nrOfAttrCalls;
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        createIPCRectInstances(pcrectClass,5);
+        // convertToDatastore should be called twice per instance = 10 times
+        assertEquals(10, PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls);
+        // convertToAttribute should not be called
+        assertEquals(0, PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls);
+
+        // Cleanup the 2nd-level cache and close the pm to make sure PCRect instances are not cached
+        pm.getPersistenceManagerFactory().getDataStoreCache().evictAll(false, pcrectClass);
+        pm.close();
+        pm = null;
+
+        nrOfDbCalls = PointToStringConverter.getNrOfConvertToDatastoreCalls();
+        nrOfAttrCalls = PointToStringConverter.getNrOfConvertToAttributeCalls();
+        pm = getPM();
+        pm.currentTransaction().begin();
+        try (Query<T> q = pm.newQuery(pcrectClass, "this.upperLeft == str")) {
+            q.declareParameters("String str");
+            q.setParameters("3:12");
+            // AttributeConverter method convertToAttribute is called when loading instance from the datastore
+            List<T> res = q.executeList();
+            assertEquals(1, res.size());
+            IPCRect rect = res.get(0);
+            Point ul = rect.getUpperLeft();
+            Point lr = rect.getLowerRight();
+
+            // Check the coordinates of the associated Point instances
+            assertEquals(UL_X+2, ul.getX());
+            assertEquals(UL_Y+2, ul.getY() == null ? 0 : ul.getY().intValue());
+            assertEquals(LR_X+2, lr.getX());
+            assertEquals(LR_Y+2, lr.getY() == null ? 0 : lr.getY().intValue());
+        } finally {
+            pm.currentTransaction().commit();
+        }
+
+        // convertToDatastore should not be called
+        assertTrue(PointToStringConverter.getNrOfConvertToDatastoreCalls() - nrOfDbCalls == 0);
+        // convertToAttribute should be called at least twice
+        assertTrue(PointToStringConverter.getNrOfConvertToAttributeCalls() - nrOfAttrCalls >= 2);
+
+    }
+
+    /**
+     * Helper method to create IPCRect instances.
+     * @param pcrectClass class instance of the IPCRect implementation class to be craeted
+     * @param nrOfObjects number of IPCRect instances to be created
+     * @return ObjectId of the first IPCRect instance
+     */
+    private <T extends IPCRect> Object createIPCRectInstances(Class<T> pcrectClass, int nrOfObjects) {
+        IPCRect rect;
+        Object oid = null;
+
+        if (nrOfObjects < 1) {
+            return null;
+        }
+
+        pm = getPM();
+        try {
+            pm.currentTransaction().begin();
+            rect = pcrectClass.getConstructor().newInstance();
+            rect.setUpperLeft(new Point(UL_X, UL_Y));
+            rect.setLowerRight(new Point(LR_X, LR_Y));
+            pm.makePersistent(rect);
+            oid = pm.getObjectId(rect);
+            for (int i = 1; i < nrOfObjects; i++) {
+                rect = pcrectClass.getConstructor().newInstance();
+                rect.setUpperLeft(new Point(UL_X + i, UL_Y + i));
+                rect.setLowerRight(new Point(LR_X + i, LR_Y + i));
+                pm.makePersistent(rect);
+            }
+            pm.currentTransaction().commit();
+        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException |
+                IllegalArgumentException | InvocationTargetException ex) {
+            fail("Error creating IPCRect instance: " + ex.getMessage());
+        } finally {
+            if (pm.currentTransaction().isActive()) {
+                pm.currentTransaction().rollback();
+            }
+        }
+        return oid;
+    }
+
+}
diff --git a/tck/src/main/java/org/apache/jdo/tck/pc/mylib/IPCRect.java b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/IPCRect.java
new file mode 100644
index 0000000..a328200
--- /dev/null
+++ b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/IPCRect.java
@@ -0,0 +1,28 @@
+/*
+ * 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.jdo.tck.pc.mylib;
+
+/**
+ * Interface for common methods of implementation classes PCRectString and PCRectStringAnnotated.
+ */
+public interface IPCRect {
+    Point getUpperLeft();
+    void setUpperLeft(Point upperLeft);
+
+    Point getLowerRight();
+    void setLowerRight(Point lowerRight);
+}
diff --git a/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectString.java b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectString.java
new file mode 100644
index 0000000..958a9e6
--- /dev/null
+++ b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectString.java
@@ -0,0 +1,70 @@
+/*
+ * 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.jdo.tck.pc.mylib;
+
+import java.util.Date;
+
+/**
+ * PersistenceCapable class to test JDO AttributeConverter interface.
+ * Its fields of type Point are converted to strings in the datastore.
+ */
+public class PCRectString implements IPCRect {
+    private static long counter = new Date().getTime();
+
+    private static synchronized long newId() {
+        return counter++;
+    }
+
+    private long id = newId();
+    private Point upperLeft;
+    private Point lowerRight;
+
+    public PCRectString() {}
+
+    public Point getUpperLeft() {
+        return upperLeft;
+    }
+    public void setUpperLeft(Point upperLeft) {
+        this.upperLeft = upperLeft;
+    }
+
+    public Point getLowerRight() {
+        return lowerRight;
+    }
+    public void setLowerRight(Point lowerRight) {
+        this.lowerRight = lowerRight;
+    }
+    public long getId() {
+        return id;
+    }
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String toString() {
+        String rc = null;
+        Object obj = this;
+        try {
+            rc = obj.getClass().getName()
+                    + " ul: " + getUpperLeft().name()
+                    + " lr: " + getLowerRight().name();
+        } catch (NullPointerException ex) {
+            rc = "NPE getting PCRectString's values";
+        }
+        return rc;
+    }
+}
diff --git a/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectStringAnnotated.java b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectStringAnnotated.java
new file mode 100644
index 0000000..ec177c1
--- /dev/null
+++ b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/PCRectStringAnnotated.java
@@ -0,0 +1,86 @@
+/*
+ * 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.jdo.tck.pc.mylib;
+
+import org.apache.jdo.tck.util.PointToStringConverter;
+
+import javax.jdo.annotations.Column;
+import javax.jdo.annotations.Convert;
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+import java.util.Date;
+
+/**
+ * PersistenceCapable class to test JDO AttributeConverter interface.
+ * Its fields of type Point are converted to strings in the datastore.
+ */
+@PersistenceCapable(table="PCRectStringAnnotated")
+public class PCRectStringAnnotated implements IPCRect {
+    private static long counter = new Date().getTime();
+
+    private static synchronized long newId() {
+        return counter++;
+    }
+
+    @Column(name="ID")
+    private long id = newId();
+
+    @Persistent
+    @Column(name="UPPER_LEFT")
+    @Convert(value = PointToStringConverter.class)
+    private Point upperLeft;
+
+    @Persistent
+    @Column(name="LOWER_RIGHT")
+    @Convert(value = PointToStringConverter.class)
+    private Point lowerRight;
+
+    public PCRectStringAnnotated() {}
+
+    public Point getUpperLeft() {
+        return upperLeft;
+    }
+    public void setUpperLeft(Point upperLeft) {
+        this.upperLeft = upperLeft;
+    }
+
+    public Point getLowerRight() {
+        return lowerRight;
+    }
+    public void setLowerRight(Point lowerRight) {
+        this.lowerRight = lowerRight;
+    }
+    public long getId() {
+        return id;
+    }
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String toString() {
+        String rc = null;
+        Object obj = this;
+        try {
+            rc = obj.getClass().getName()
+                    + " ul: " + getUpperLeft().name()
+                    + " lr: " + getLowerRight().name();
+        } catch (NullPointerException ex) {
+            rc = "NPE getting PCRectString's values";
+        }
+        return rc;
+    }
+}
diff --git a/tck/src/main/java/org/apache/jdo/tck/pc/mylib/Point.java b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/Point.java
index 572fba8..a49d71f 100644
--- a/tck/src/main/java/org/apache/jdo/tck/pc/mylib/Point.java
+++ b/tck/src/main/java/org/apache/jdo/tck/pc/mylib/Point.java
@@ -40,10 +40,8 @@ public class Point {
 
     public String toString() {
         String rc = null;
-	Object obj = this;
         try {
-	    rc = obj.getClass().getName();
-            //rc = Util.getClassName(this) + name();
+            rc = "Point(" + name() + ")";
         } catch (NullPointerException ex) {
             rc = "NPE getting Point's values";
         }
@@ -61,6 +59,7 @@ public class Point {
     }
     
     public String name() {
-        return " x: " + getX() + ", y: " + getY().intValue();
+        return "x: " + getX() + ", y: " + getY().intValue();
     }
+
 }
diff --git a/tck/src/main/java/org/apache/jdo/tck/util/PointToStringConverter.java b/tck/src/main/java/org/apache/jdo/tck/util/PointToStringConverter.java
new file mode 100644
index 0000000..eeda26f
--- /dev/null
+++ b/tck/src/main/java/org/apache/jdo/tck/util/PointToStringConverter.java
@@ -0,0 +1,100 @@
+/*
+ * 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.jdo.tck.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jdo.tck.pc.mylib.Point;
+
+import javax.jdo.AttributeConverter;
+
+/**
+ * AttributeConverter implementation mapping a Point instance to a string of the form x:y.
+ */
+public class PointToStringConverter implements AttributeConverter<Point, String> {
+
+    private static int nrOfConvertToDatastoreCalls = 0;
+    private static int nrOfConvertToAttributeCalls = 0;
+
+    // Character to separate x and y value of the Point instance.
+    private static final String SEPARATOR = ":";
+
+    private Log logger = LogFactory.getFactory().getInstance("org.apache.jdo.tck");
+
+    /**
+     * Converts the given Point attribute value to its string representation in the datastore.
+     * @param attributeValue the attribute value of type Point to be converted
+     * @return the string representation of the Point instance
+     */
+    @Override
+    public String convertToDatastore(Point attributeValue) {
+        nrOfConvertToDatastoreCalls++;
+        String datastoreValue = null;
+        if (attributeValue != null) {
+            StringBuilder builder = new StringBuilder();
+            builder.append(attributeValue.getX());
+            builder.append(SEPARATOR);
+            builder.append(attributeValue.getY() == null ? Integer.valueOf(0) : attributeValue.getY());
+            datastoreValue = builder.toString();
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("PointToStringConverter.convertToDatastore " +
+                    "attributeValue=" + attributeValue + " datastoreValue=" + datastoreValue);
+        }
+        return datastoreValue;
+    }
+
+    /**
+     * Converts the given string datastore value to its representation as a persistent attribute of type Point.
+     * @param datastoreValue the string value in the datastore
+     * @return the attribute value as Point instance
+     */
+    @Override
+    public Point convertToAttribute(String datastoreValue) {
+        nrOfConvertToAttributeCalls++;
+        Point attributeValue = null;
+        if (datastoreValue != null) {
+            String[] parts = datastoreValue.split(SEPARATOR);
+            if (parts.length == 2) {
+                Integer x = Integer.valueOf(parts[0]);
+                Integer y = Integer.valueOf(parts[1]);
+                attributeValue = new Point(x == null ? 0 : x.intValue(), y);
+            }
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("PointToStringConverter.convertToAttribute " +
+                    "datastoreValue=" + datastoreValue + " attributeValue=" + attributeValue);
+        }
+        return attributeValue;
+    }
+
+    /**
+     * Method returning the current number of convertToDatastore method calls.
+     * @return number of convertToDatastore method calls
+     */
+    public static int getNrOfConvertToDatastoreCalls() {
+        return nrOfConvertToDatastoreCalls;
+    }
+
+    /**
+     * Method returning the current number of convertToAttribute method calls.
+     * @return number of convertToAttribute method calls
+     */
+    public static int getNrOfConvertToAttributeCalls() {
+        return nrOfConvertToAttributeCalls;
+    }
+}
diff --git a/tck/src/main/resources/conf/configurations.list b/tck/src/main/resources/conf/configurations.list
index ca46d57..9382a72 100644
--- a/tck/src/main/resources/conf/configurations.list
+++ b/tck/src/main/resources/conf/configurations.list
@@ -25,6 +25,7 @@ jdo.tck.cfglist = \
                  extents.conf \
                  fetchplan.conf \
                  fetchgroup.conf \
+                 converter.conf \
                  lifecycle.conf \
                  models.conf \
                  models1.conf \
diff --git a/tck/src/main/resources/conf/converter.conf b/tck/src/main/resources/conf/converter.conf
new file mode 100644
index 0000000..921a371
--- /dev/null
+++ b/tck/src/main/resources/conf/converter.conf
@@ -0,0 +1,23 @@
+# 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.
+
+jdo.tck.description = Converter test, standard mapping, no testdata.
+jdo.tck.mapping.companyfactory =
+jdo.tck.testdata =
+jdo.tck.standarddata =
+jdo.tck.mapping = 0
+jdo.tck.requiredOptions =
+jdo.tck.classes = \
+org.apache.jdo.tck.api.converter.AttributeConverterTest
diff --git a/tck/src/main/resources/jdo/applicationidentity/org/apache/jdo/tck/pc/mylib/package.jdo b/tck/src/main/resources/jdo/applicationidentity/org/apache/jdo/tck/pc/mylib/package.jdo
index 0a4e695..2330581 100644
--- a/tck/src/main/resources/jdo/applicationidentity/org/apache/jdo/tck/pc/mylib/package.jdo
+++ b/tck/src/main/resources/jdo/applicationidentity/org/apache/jdo/tck/pc/mylib/package.jdo
@@ -52,6 +52,18 @@
       </fetch-group>
     </class>
 
+    <class name="PCRectString"
+           identity-type="application" objectid-class="javax.jdo.identity.LongIdentity">
+      <field name="id" primary-key="true"/>
+      <field name="upperLeft" converter="org.apache.jdo.tck.util.PointToStringConverter"/>
+      <field name="lowerRight" converter="org.apache.jdo.tck.util.PointToStringConverter"/>
+    </class>
+
+    <class name="PCRectStringAnnotated"
+           identity-type="application" objectid-class="javax.jdo.identity.LongIdentity">
+      <field name="id" primary-key="true"/>
+    </class>
+
     <class name="PrimitiveTypes" 
            identity-type="application" objectid-class="org.apache.jdo.tck.pc.mylib.PrimitiveTypes$Oid">
       <field name="id" primary-key="true"/>
diff --git a/tck/src/main/resources/jdo/datastoreidentity/org/apache/jdo/tck/pc/mylib/package.jdo b/tck/src/main/resources/jdo/datastoreidentity/org/apache/jdo/tck/pc/mylib/package.jdo
index 99e376f..f5da7db 100644
--- a/tck/src/main/resources/jdo/datastoreidentity/org/apache/jdo/tck/pc/mylib/package.jdo
+++ b/tck/src/main/resources/jdo/datastoreidentity/org/apache/jdo/tck/pc/mylib/package.jdo
@@ -43,6 +43,13 @@
       </fetch-group>
     </class>
 
+    <class name="PCRectStringAnnotated" identity-type="datastore"/>
+
+    <class name="PCRectString" identity-type="datastore">
+      <field name="upperLeft" converter="org.apache.jdo.tck.util.PointToStringConverter"/>
+      <field name="lowerRight" converter="org.apache.jdo.tck.util.PointToStringConverter"/>
+    </class>
+
     <class name="PrimitiveTypes" identity-type="datastore"/>
 
   </package>
diff --git a/tck/src/main/resources/orm/applicationidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm b/tck/src/main/resources/orm/applicationidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
index 053bbc7..0a7b45c 100644
--- a/tck/src/main/resources/orm/applicationidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
+++ b/tck/src/main/resources/orm/applicationidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
@@ -49,6 +49,12 @@
       </field>
     </class>
 
+    <class name="PCRectString" table="PCRectString">
+      <field name="id" column="ID"/>
+      <field name="lowerRight" column="LOWER_RIGHT"/>
+      <field name="upperLeft" column="UPPER_LEFT"/>
+    </class>
+
     <class name="PrimitiveTypes" table="PrimitiveTypes">
       <field name="id" column="ID"/>
       <field name="booleanNotNull" column="booleanNotNull"/>
diff --git a/tck/src/main/resources/orm/datastoreidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm b/tck/src/main/resources/orm/datastoreidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
index 3ca561e..e7f1ac9 100644
--- a/tck/src/main/resources/orm/datastoreidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
+++ b/tck/src/main/resources/orm/datastoreidentity/org/apache/jdo/tck/pc/mylib/package-standard.orm
@@ -53,6 +53,17 @@
       </field>
     </class>
 
+    <class name="PCRectString" table="PCRectString">
+      <datastore-identity strategy="identity" column="DATASTORE_IDENTITY"/>
+      <field name="id" column="ID"/>
+      <field name="lowerRight" column="LOWER_RIGHT"/>
+      <field name="upperLeft" column="UPPER_LEFT"/>
+    </class>
+
+    <class name="PCRectStringAnnotated">
+      <datastore-identity strategy="identity" column="DATASTORE_IDENTITY"/>
+    </class>
+
     <class name="PrimitiveTypes" table="PrimitiveTypes">
       <datastore-identity strategy="identity" column="DATASTORE_IDENTITY"/>
       <field name="id" column="ID"/>
diff --git a/tck/src/main/resources/sql/derby/applicationidentity/schema.sql b/tck/src/main/resources/sql/derby/applicationidentity/schema.sql
index c68b0f2..4596103 100644
--- a/tck/src/main/resources/sql/derby/applicationidentity/schema.sql
+++ b/tck/src/main/resources/sql/derby/applicationidentity/schema.sql
@@ -61,6 +61,8 @@ CREATE TABLE Item (
 -------------------------
 
 DROP TABLE PCRect;
+DROP TABLE PCRectString;
+DROP TABLE PCRectStringAnnotated;
 DROP TABLE PCPoint;
 DROP TABLE VersionedPCPoint;
 DROP TABLE PCPoint2;
@@ -96,6 +98,20 @@ CREATE TABLE PCRect (
     CONSTRAINT PCRCT_CONST PRIMARY KEY (ID)
 );
 
+CREATE TABLE PCRectString (
+    ID BIGINT NOT NULL,
+    UPPER_LEFT VARCHAR(30),
+    LOWER_RIGHT VARCHAR(30),
+    CONSTRAINT PCRCTSTR_CONST PRIMARY KEY (ID)
+);
+
+CREATE TABLE PCRectStringAnnotated (
+    ID BIGINT NOT NULL,
+    UPPER_LEFT VARCHAR(30),
+    LOWER_RIGHT VARCHAR(30),
+    CONSTRAINT PCRCTANN_CONST PRIMARY KEY (ID)
+);
+
 CREATE TABLE PrimitiveTypes (
     ID BIGINT NOT NULL,
     booleanNotNull CHAR(1) NOT NULL CHECK (booleanNotNull IN ('Y','N')),
diff --git a/tck/src/main/resources/sql/derby/datastoreidentity/schema.sql b/tck/src/main/resources/sql/derby/datastoreidentity/schema.sql
index dfd4902..7fc3f17 100644
--- a/tck/src/main/resources/sql/derby/datastoreidentity/schema.sql
+++ b/tck/src/main/resources/sql/derby/datastoreidentity/schema.sql
@@ -41,6 +41,8 @@ CREATE TABLE address (
 -------------------------
 
 DROP TABLE PCRect;
+DROP TABLE PCRectString;
+DROP TABLE PCRectStringAnnotated;
 DROP TABLE PCPoint;
 DROP TABLE VersionedPCPoint;
 DROP TABLE PCPoint2;
@@ -80,6 +82,22 @@ CREATE TABLE PCRect (
     CONSTRAINT PCRCT_CONST PRIMARY KEY (DATASTORE_IDENTITY)
 );
 
+CREATE TABLE PCRectString (
+    DATASTORE_IDENTITY BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY,
+    ID BIGINT,
+    UPPER_LEFT VARCHAR(30),
+    LOWER_RIGHT VARCHAR(30),
+    CONSTRAINT PCRCTSTR_CONST PRIMARY KEY (DATASTORE_IDENTITY)
+);
+
+CREATE TABLE PCRectStringAnnotated (
+    DATASTORE_IDENTITY BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY,
+    ID BIGINT,
+    UPPER_LEFT VARCHAR(30),
+    LOWER_RIGHT VARCHAR(30),
+    CONSTRAINT PCRCTANN_CONST PRIMARY KEY (DATASTORE_IDENTITY)
+);
+
 CREATE TABLE PrimitiveTypes (
     DATASTORE_IDENTITY BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY,
     ID BIGINT,