You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by dw...@apache.org on 2010/03/05 23:22:35 UTC

svn commit: r919649 - in /openjpa/branches/1.3.x: openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ openjpa-kernel/src/main/java/org/apache/openjpa/meta/ openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/ openjpa-persis...

Author: dwoods
Date: Fri Mar  5 22:22:35 2010
New Revision: 919649

URL: http://svn.apache.org/viewvc?rev=919649&view=rev
Log:
OPENJPA-1097 Detachment processing of our proxied mutable types (Date, Timestamp, etc) needs to be consistent as EM.clear() was not unproxying JavaTypes.OBJECT fields but detach()/detachAll() were

Added:
    openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java   (with props)
    openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java   (with props)
Modified:
    openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachManager.java
    openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java
    openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java

Modified: openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachManager.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachManager.java?rev=919649&r1=919648&r2=919649&view=diff
==============================================================================
--- openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachManager.java (original)
+++ openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/DetachManager.java Fri Mar  5 22:22:35 2010
@@ -539,28 +539,28 @@
          * Unproxies second class object fields.
          */
         public void reproxy(DetachedStateManager dsm) {
-            FieldMetaData[] fmds = sm.getMetaData().getFields();
-            for (int i = 0; i < fmds.length; i++) {
-                switch (fmds[i].getDeclaredTypeCode()) {
+            for (FieldMetaData fmd : sm.getMetaData().getProxyFields()) {
+                switch (fmd.getDeclaredTypeCode()) {
                 case JavaTypes.COLLECTION:
                 case JavaTypes.MAP:
                     // lrs proxies not detached
-                    if (fmds[i].isLRS()) {
+                    if (fmd.isLRS()) {
                         objval = null;
-                        sm.replaceField(getDetachedPersistenceCapable(), 
-                            this, i);
+                        sm.replaceField(getDetachedPersistenceCapable(), this, fmd.getIndex());
                         break;
                     }
                     // no break
                 case JavaTypes.CALENDAR:
                 case JavaTypes.DATE:
                 case JavaTypes.OBJECT:
-                    sm.provideField(getDetachedPersistenceCapable(), this, i);
+                    sm.provideField(getDetachedPersistenceCapable(), this, fmd.getIndex());
                     if (objval instanceof Proxy) {
                         Proxy proxy = (Proxy) objval;
                         if (proxy.getChangeTracker() != null)
                             proxy.getChangeTracker().stopTracking();
-                        proxy.setOwner(dsm, (dsm == null) ? -1 : i);
+                        proxy.setOwner(dsm, (dsm == null) ? -1 : fmd.getIndex());
+                        objval = proxy.copy(proxy);
+                        sm.replaceField(getDetachedPersistenceCapable(), this, fmd.getIndex());
                     }
                 }
             }

Modified: openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java?rev=919649&r1=919648&r2=919649&view=diff
==============================================================================
--- openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java (original)
+++ openjpa/branches/1.3.x/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java Fri Mar  5 22:22:35 2010
@@ -194,6 +194,7 @@
     private FieldMetaData[] _definedFields = null;
     private FieldMetaData[] _listingFields = null;
     private FieldMetaData[] _allListingFields = null;
+    private FieldMetaData[] _allProxyFields = null;
     private FetchGroup[] _fgs = null;
     private FetchGroup[] _customFGs = null;
     private boolean _intercepting = false;
@@ -912,6 +913,39 @@
     }
 
     /**
+     * Return all fields that are types that need to be wrappered by a proxy.
+     * The types that need to be proxied are:
+     * <p>
+     *  <li>org.apache.openjpa.meta.JavaTypes.CALENDAR
+     *  <li>org.apache.openjpa.meta.JavaTypes.COLLECTION
+     *  <li>org.apache.openjpa.meta.JavaTypes.DATE
+     *  <li>org.apache.openjpa.meta.JavaTypes.MAP
+     *  <li>org.apache.openjpa.meta.JavaTypes.OBJECT
+     */
+    public FieldMetaData[] getProxyFields() {
+        if (_allProxyFields == null) {
+            // Make sure _allFields has been initialized
+            if (_allFields == null) {
+                getFields();
+            }
+            List<FieldMetaData> res = new ArrayList<FieldMetaData>();
+            for (FieldMetaData fmd : _allFields) {
+                switch (fmd.getDeclaredTypeCode()) {
+                    case JavaTypes.CALENDAR:
+                    case JavaTypes.COLLECTION:
+                    case JavaTypes.DATE:
+                    case JavaTypes.MAP:
+                    case JavaTypes.OBJECT:
+                        res.add(fmd);
+                        break;
+                }
+            }
+            _allProxyFields = res.toArray(new FieldMetaData[res.size()]);
+        }
+        return _allProxyFields;
+    }
+
+    /**
      * Return all field metadatas, including superclass fields.
      */
     public FieldMetaData[] getFields() {
@@ -1523,6 +1557,7 @@
         _allFields = null;
         _allDFGFields = null;
         _allPKFields = null;
+        _allProxyFields = null;
         _definedFields = null;
         _listingFields = null;
         _allListingFields = null;

Added: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java?rev=919649&view=auto
==============================================================================
--- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java (added)
+++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java Fri Mar  5 22:22:35 2010
@@ -0,0 +1,107 @@
+/*
+ * 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.detach;
+
+import java.io.Serializable;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+@Table(name="Entity20_detach")
+public class Entity20 implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+
+    @Id
+    private Integer id;
+    
+    @Column(name = "sqldate" ) 
+    @Temporal(TemporalType.DATE) 
+    private Date sqlDate; 
+
+    @Column(name = "sqltime") 
+    @Temporal(TemporalType.TIME)
+    private Time sqlTime; 
+
+    @Column(name = "sqltimestamp") 
+    @Temporal(TemporalType.TIMESTAMP)
+    private Timestamp sqlTimestamp; 
+
+    private String name;
+
+    public Entity20() {
+    }
+    
+    public Entity20(int id) {
+        this.id = new Integer(id);
+        this.name = this.id.toString();
+        Long time = System.currentTimeMillis();
+        this.sqlTime = new Time(time);
+        this.sqlDate = new Date(time);
+        this.sqlTimestamp = new Timestamp(time);
+    }
+    
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setDate(Date d) {
+        sqlDate = d;
+    }
+    
+    public Date getDate() {
+        return sqlDate;
+    }
+    
+    public void setTime(Time t) {
+        sqlTime = t;
+    }
+    
+    public Time getTime() {
+        return sqlTime;
+    }
+    
+    public void setTimestamp(Timestamp t) {
+        sqlTimestamp = t;
+    }
+    
+    public Timestamp getTimestamp() {
+        return sqlTimestamp;
+    }
+}

Propchange: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/Entity20.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java?rev=919649&view=auto
==============================================================================
--- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java (added)
+++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java Fri Mar  5 22:22:35 2010
@@ -0,0 +1,274 @@
+/*
+ * 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.detach;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+import org.apache.openjpa.conf.Compatibility;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+public class TestDetachNoProxy extends SingleEMFTestCase {
+    
+    private static final int numEntities = 3;
+    private static final String PROXY = new String("$proxy");
+    private Log log;
+    
+    public void setUp() {
+        setUp(DROP_TABLES, Entity20.class);
+        log = emf.getConfiguration().getLog("test");
+        
+        /* This code is only for 2.0 and later
+        Compatibility compat = emf.getConfiguration().getCompatibilityInstance();
+        assertNotNull(compat);
+        if (log.isTraceEnabled()) {
+            log.info("Before set, FlushBeforeDetach=" + compat.getFlushBeforeDetach());
+            log.info("Before set, CopyOnDetach=" + compat.getCopyOnDetach());
+            log.info("Before set, CascadeWithDetach=" + compat.getCascadeWithDetach());
+        }
+        compat.setFlushBeforeDetach(false);
+        compat.setCopyOnDetach(false);
+        compat.setCascadeWithDetach(false);
+        if (log.isTraceEnabled()) {
+            log.info("After set, FlushBeforeDetach=" + compat.getFlushBeforeDetach());
+            log.info("After set, CopyOnDetach=" + compat.getCopyOnDetach());
+            log.info("After set, CascadeWithDetach=" + compat.getCascadeWithDetach());
+        }
+        */
+        createEntities(numEntities);
+    }
+    
+    private void createEntities(int count) {
+        Entity20 e20 = null;
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.getTransaction().begin();
+        for (int i=0; i<count; i++) {
+            e20 = new Entity20(i);
+            em.persist(e20);
+        }
+        em.getTransaction().commit();
+        em.close();
+    }
+    
+    /* 
+     * Verify that returned copy of detached entity does not use the proxy classes.
+     */
+    public void testDetach() {
+        if (log.isTraceEnabled())
+            log.info("***** testDetach() *****");
+        Integer id = new Integer(0);
+        OpenJPAEntityManager em = emf.createEntityManager();
+        
+        em.clear();
+        Entity20 e20 = em.find(Entity20.class, id);
+        if (log.isTraceEnabled())
+            log.trace("** after find");
+        assertTrue(em.contains(e20));
+        verifySerializable(e20, true);
+        
+        // pre openjpa-2.0.0 behavior, where detach() returned the updated entity
+        Entity20 e20Copy = em.detach(e20);
+        if (log.isTraceEnabled())
+            log.trace("** after detach");
+        // original entity should have proxy classes and should not be detached
+        assertFalse(em.isDetached(e20));
+        verifySerializable(e20, true);
+        // returned entity should not have any proxy classes and should be detached
+        assertTrue(em.isDetached(e20Copy));
+        verifySerializable(e20Copy, false);
+               
+        em.close();
+    }
+
+    /* 
+     * This only works on 2.0.0 and later - new method
+     * Verify that a detachCopy() returned entity does not contain any proxy classes.
+     *
+    public void testDetachCopy() {
+        if (log.isTraceEnabled())
+            log.info("***** testDetachCopy() *****");
+        Integer id = new Integer(0);
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.clear();
+
+        Entity20 e20 = em.find(Entity20.class, id);
+        if (log.isTraceEnabled())
+            log.trace("** after find");
+        assertTrue(em.contains(e20));
+        verifySerializable(e20, true);
+                        
+        // This only works on 2.0 and later - new method
+        Entity20 e20copy = em.detachCopy(e20);
+        if (log.isTraceEnabled())
+            log.trace("** after detachCopy");
+        assertTrue(em.isDetached(e20copy));
+        verifySerializable(e20copy, false);
+        
+        em.close();
+    }
+    */
+
+    /*
+     * Verify that returned copies of detachAll() entities do not use the proxy classes.
+     */
+    public void testDetachAll() {
+        if (log.isTraceEnabled())
+            log.info("***** testDetachAll() *****");
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.clear();
+
+        ArrayList<Entity20> e20List = new ArrayList<Entity20>(numEntities);
+        for (int i=0; i<numEntities; i++) {
+            Entity20 e20 = em.find(Entity20.class, new Integer(i));
+            e20List.add(e20);
+            if (log.isTraceEnabled())
+                log.trace("** after find Entity20(" + i + ")");
+            assertTrue(em.contains(e20));
+            verifySerializable(e20, true);            
+        }
+        
+        // pre openjpa-2.0.0 behavior, where detachAll() returned the updated entities
+        ArrayList<Entity20> e20ListCopy = new ArrayList<Entity20>(em.detachAll(e20List));
+        for (int i=0; i<numEntities; i++) {
+            if (log.isTraceEnabled())
+                log.trace("** after EM.detachAll() verify e20List(" + i + ")");
+            Entity20 e20 = e20List.get(i);
+            // original entity should have proxy classes and should not be detached
+            assertFalse(em.isDetached(e20));
+            verifySerializable(e20, true);
+        }
+        for (int i=0; i<numEntities; i++) {
+            if (log.isTraceEnabled())
+                log.trace("** after EM.detachAll() verify e20ListCopy(" + i + ")");
+            Entity20 e20 = e20ListCopy.get(i);
+            // entity should not have any proxy classes and should be detached
+            assertTrue(em.isDetached(e20));
+            verifySerializable(e20, false);
+        }
+
+        em.close();
+    }
+
+    /*
+     * Verify that after EM.clear() in-place detached entities do not contain any proxy classes.
+     */
+    public void testClear() {
+        if (log.isTraceEnabled())
+            log.info("***** testClear() *****");
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.clear();
+
+        ArrayList<Entity20> e20List = new ArrayList<Entity20>(numEntities);
+        for (int i=0; i<numEntities; i++) {
+            Entity20 e20 = em.find(Entity20.class, new Integer(i));
+            e20List.add(e20);
+            if (log.isTraceEnabled())
+                log.trace("** after find Entity20(" + i + ")");
+            assertTrue(em.contains(e20));
+            verifySerializable(e20, true);            
+        }
+
+        em.clear();
+        for (int i=0; i<numEntities; i++) {
+            if (log.isTraceEnabled())
+                log.trace("** after EM.clear() verify Entity20(" + i + ")");
+            Entity20 e20 = e20List.get(i);
+            assertTrue(em.isDetached(e20));
+            verifySerializable(e20, false);
+        }
+
+        em.close();
+    }
+
+    
+    private void verifySerializable(Entity20 e20, boolean usesProxy) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = null;
+        byte[] e20bytes = null;
+        
+        if (log.isTraceEnabled())
+            log.trace("verifySerializable() - before serialize");
+        verifyEntities(e20, usesProxy);
+
+        // first serialize
+        try {
+            oos = new ObjectOutputStream(baos);
+            oos.writeObject(e20);
+            e20bytes = baos.toByteArray();
+        } catch (IOException e) {
+            fail(e.toString());
+        } finally {
+            try {
+                if (oos != null)
+                    oos.close();
+            } catch (IOException e) {
+            }
+        }
+        
+        // then deserialize
+        ByteArrayInputStream bais = new ByteArrayInputStream(e20bytes);
+        ObjectInputStream ois = null;
+        Entity20 e20new = null;
+        try {
+            ois = new ObjectInputStream(bais);
+            e20new = (Entity20) ois.readObject();
+            if (log.isTraceEnabled())
+                log.trace("verifySerializable() - after deserialize");
+            verifyEntities(e20new, false);
+        } catch (IOException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } finally {
+            try {
+                if (ois != null)
+                    ois.close();
+            } catch (IOException e) {
+            }
+        }
+
+    }
+
+    private void verifyEntities(Entity20 e20, boolean usesProxy) {
+        if (log.isTraceEnabled()) {
+            printClassNames(e20);
+            log.trace("asserting expected proxy usage");
+        }
+        assertTrue("Expected sqlDate endsWith($proxy) to return " + usesProxy,
+            usesProxy == e20.getDate().getClass().getCanonicalName().endsWith(PROXY));
+        assertTrue("Expected sqlTime endsWith($proxy) to return " + usesProxy,
+            usesProxy == e20.getTime().getClass().getCanonicalName().endsWith(PROXY));
+        assertTrue("Expected sqlTimestamp endsWith($proxy) to return " + usesProxy,
+            usesProxy == e20.getTimestamp().getClass().getCanonicalName().endsWith(PROXY));
+        
+    }
+    
+    private void printClassNames(Entity20 e20) {
+        log.info("sqlDate = " + e20.getDate().getClass().getCanonicalName());
+        log.info("sqlTime = " + e20.getTime().getClass().getCanonicalName());
+        log.info("sqlTimestamp = " + e20.getTimestamp().getClass().getCanonicalName());
+    }
+    
+}

Propchange: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/detach/TestDetachNoProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java?rev=919649&r1=919648&r2=919649&view=diff
==============================================================================
--- openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java (original)
+++ openjpa/branches/1.3.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/proxy/TestProxyCollection.java Fri Mar  5 22:22:35 2010
@@ -124,10 +124,12 @@
 		modified.modify(modifier);
 		em.merge(modified);
 		em.getTransaction().commit();
-		em.clear();
-		
-		assertProxyCollection(root.getNodes(), false);
-		
+
+        // this was unproxied by EM.clear() in create() below
+        // assertProxyCollection(root.getNodes(), true);
+        assertNotProxyCollection(root.getNodes());
+
+	    em.clear();
 		verify(root, modifier);
 	}
 	
@@ -145,7 +147,11 @@
 		em.getTransaction().begin();
 		em.persist(root);
 		em.getTransaction().commit();
-		em.clear();
+		// OPENJPA-1097 Fixed behavior so entities will use the proxy classes until EM.clear() is called
+        assertProxyCollection(root.getNodes(), true);
+        em.clear();
+        // OPENJPA-1097 All proxies are removed after EM.clear()
+        assertNotProxyCollection(root.getNodes());
 		
 		return root;
 	}
@@ -180,4 +186,11 @@
 			assertFalse(tracker.isTracking());
 		}
 	}
+
+    /**
+     * Asserts that the given object is NOT a proxy collection.
+     */
+    void assertNotProxyCollection(Object o) {
+        assertFalse(o instanceof ProxyCollection);
+    }
 }