You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by jl...@apache.org on 2013/05/27 09:38:40 UTC

svn commit: r1486529 - in /ofbiz/branches/release11.04: ./ framework/entity/entitydef/ framework/entity/src/org/ofbiz/entity/ framework/entity/src/org/ofbiz/entity/cache/ framework/entity/src/org/ofbiz/entity/eca/ framework/entity/src/org/ofbiz/entity/...

Author: jleroux
Date: Mon May 27 07:38:39 2013
New Revision: 1486529

URL: http://svn.apache.org/r1486529
Log:
Backports r1486304,1486310,1486313 from branch 12.04 (wich was merged by Adrian from trunk)
r1486304: Backported GenericEntity immutable fixes from the trunk.
r1486310: Backported entity cache fixes. This commit breaks the rule of no API changes in a release, but I am making an exception here because the methods that were removed should NEVER be used because they GUARANTEE data corruption
r1486313: Marked some Delegator methods as deprecated because they should not be used

Modified:
    ofbiz/branches/release11.04/   (props changed)
    ofbiz/branches/release11.04/framework/entity/entitydef/entitymodel_test.xml
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/Delegator.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericEntity.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericValue.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/AbstractEntityConditionCache.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/Cache.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityListCache.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/eca/EntityEcaHandler.java
    ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java
    ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelForm.java
    ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelFormField.java

Propchange: ofbiz/branches/release11.04/
------------------------------------------------------------------------------
  Merged /ofbiz/trunk:r1471283-1471284,1471687,1471739,1476296,1480407,1481287,1484279
  Merged /ofbiz/branches/release12.04:r1486304,1486310,1486313

Modified: ofbiz/branches/release11.04/framework/entity/entitydef/entitymodel_test.xml
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/entitydef/entitymodel_test.xml?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/entitydef/entitymodel_test.xml (original)
+++ ofbiz/branches/release11.04/framework/entity/entitydef/entitymodel_test.xml Mon May 27 07:38:39 2013
@@ -71,6 +71,27 @@ under the License.
         <prim-key field="testingTypeId"/>
     </entity>
   <!-- =========================================================
+    Used for testing views with pks.
+    This simulates the Party->PartyGroup and Party->Person entity relationships.
+  ========================================================= -->
+    <entity entity-name="TestingSubtype" package-name="org.ofbiz.entity.test" title="Testing Subtype Entity">
+        <field name="testingTypeId" type="id-ne" />
+        <field name="subtypeDescription" type="description" />
+        <prim-key field="testingTypeId" />
+    </entity>
+  <!-- =========================================================
+    Used for testing views with pks
+  ========================================================= -->
+    <view-entity entity-name="TestingViewPks" package-name="org.ofbiz.entity.test" title="Testing And TestingSubtype View">
+        <member-entity entity-alias="TST" entity-name="TestingType" />
+        <member-entity entity-alias="TSTSUB" entity-name="TestingSubtype" />
+        <alias-all entity-alias="TST" />
+        <alias-all entity-alias="TSTSUB" />
+        <view-link entity-alias="TST" rel-entity-alias="TSTSUB">
+            <key-map field-name="testingTypeId" />
+        </view-link>
+    </view-entity>
+  <!-- =========================================================
      An entity for testing the BLOB data type
    ========================================================== -->
     <entity entity-name="TestBlob"

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/Delegator.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/Delegator.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/Delegator.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/Delegator.java Mon May 27 07:38:39 2013
@@ -157,9 +157,11 @@ public interface Delegator {
      * @param primaryKey
      *            The GenericPK to create a value in the datasource from
      * @param doCacheClear
-     *            boolean that specifies whether to clear related cache entries
-     *            for this primaryKey to be created
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return GenericValue instance containing the new instance
+     * @deprecated
      */
     public GenericValue create(GenericPK primaryKey, boolean doCacheClear) throws GenericEntityException;
 
@@ -181,8 +183,10 @@ public interface Delegator {
      *            The GenericValue to create a value in the datasource from
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return GenericValue instance containing the new instance
+     * @deprecated
      */
     public GenericValue create(GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -220,8 +224,10 @@ public interface Delegator {
      *            instance
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return GenericValue instance containing the new or updated instance
+     * @deprecated
      */
     public GenericValue createOrStore(GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -864,7 +870,9 @@ public interface Delegator {
      *            GenericValue instance containing the entity to refresh
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
+     * @deprecated
      */
     public void refresh(GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -913,8 +921,10 @@ public interface Delegator {
      *            or by and fields to remove
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeAll(List<? extends GenericEntity> dummyPKs, boolean doCacheClear) throws GenericEntityException;
 
@@ -927,12 +937,14 @@ public interface Delegator {
      * @param entityName
      *            The Name of the Entity as defined in the entity XML file
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            value to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @param fields
      *            The fields of the named entity to query by with their
      *            corresponding values
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeByAnd(String entityName, boolean doCacheClear, Object... fields) throws GenericEntityException;
 
@@ -959,9 +971,11 @@ public interface Delegator {
      *            The fields of the named entity to query by with their
      *            corresponding values
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            value to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeByAnd(String entityName, Map<String, ? extends Object> fields, boolean doCacheClear) throws GenericEntityException;
 
@@ -997,9 +1011,11 @@ public interface Delegator {
      * @param condition
      *            The condition used to restrict the removing
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            value to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeByCondition(String entityName, EntityCondition condition, boolean doCacheClear) throws GenericEntityException;
 
@@ -1018,9 +1034,11 @@ public interface Delegator {
      * @param primaryKey
      *            The primary key of the entity to remove.
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            primaryKey to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeByPrimaryKey(GenericPK primaryKey, boolean doCacheClear) throws GenericEntityException;
 
@@ -1049,9 +1067,11 @@ public interface Delegator {
      * @param value
      *            GenericValue instance containing the entity
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            value to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeRelated(String relationName, GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -1070,9 +1090,11 @@ public interface Delegator {
      * @param value
      *            The GenericValue object of the entity to remove.
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for this
-     *            value to be removed
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int removeValue(GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -1113,8 +1135,10 @@ public interface Delegator {
      *            GenericValue instance containing the entity
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int store(GenericValue value, boolean doCacheClear) throws GenericEntityException;
 
@@ -1150,8 +1174,10 @@ public interface Delegator {
      *            store
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int storeAll(List<GenericValue> values, boolean doCacheClear) throws GenericEntityException;
 
@@ -1170,11 +1196,13 @@ public interface Delegator {
      *            store
      * @param doCacheClear
      *            boolean that specifies whether or not to automatically clear
-     *            cache entries related to this operation
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @param createDummyFks
      *            boolean that specifies whether or not to automatically create
      *            "dummy" place holder FKs
      * @return int representing number of rows effected by this operation
+     * @deprecated
      */
     public int storeAll(List<GenericValue> values, boolean doCacheClear, boolean createDummyFks) throws GenericEntityException;
 
@@ -1202,10 +1230,12 @@ public interface Delegator {
      * @param condition
      *            The condition that restricts the list of stored values
      * @param doCacheClear
-     *            boolean that specifies whether to clear cache entries for
-     *            these values
+     *            boolean that specifies whether or not to automatically clear
+     *            cache entries related to this operation. This should always be
+     *            <code>true</code> - otherwise you will lose data integrity.
      * @return int representing number of rows effected by this operation
      * @throws GenericEntityException
+     * @deprecated
      */
     public int storeByCondition(String entityName, Map<String, ? extends Object> fieldsToSet, EntityCondition condition, boolean doCacheClear) throws GenericEntityException;
    

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericDelegator.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericDelegator.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericDelegator.java Mon May 27 07:38:39 2013
@@ -975,12 +975,6 @@ public class GenericDelegator implements
 
             GenericHelper helper = getEntityHelper(primaryKey.getEntityName());
 
-            if (doCacheClear) {
-                // always clear cache before the operation
-                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_REMOVE, primaryKey, false);
-                this.clearCacheLine(primaryKey);
-            }
-
             ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_REMOVE, primaryKey, false);
 
             // if audit log on for any fields, save old value before removing so it's still there
@@ -990,9 +984,14 @@ public class GenericDelegator implements
 
             GenericValue removedEntity = null;
             if (testMode) {
-                removedEntity = this.findOne(primaryKey.entityName, primaryKey, false);
+                removedEntity = this.findOne(primaryKey.getEntityName(), primaryKey, false);
             }
             int num = helper.removeByPrimaryKey(primaryKey);
+            if (doCacheClear) {
+                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_REMOVE, primaryKey, false);
+                this.clearCacheLine(primaryKey);
+            }
+
             this.saveEntitySyncRemoveInfo(primaryKey);
 
             if (testMode) {
@@ -1044,11 +1043,6 @@ public class GenericDelegator implements
 
             GenericHelper helper = getEntityHelper(value.getEntityName());
 
-            if (doCacheClear) {
-                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_REMOVE, value, false);
-                this.clearCacheLine(value);
-            }
-
             ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_REMOVE, value, false);
 
             // if audit log on for any fields, save old value before actual remove
@@ -1062,6 +1056,13 @@ public class GenericDelegator implements
             }
 
             int num = helper.removeByPrimaryKey(value.getPrimaryKey());
+            // Need to call removedFromDatasource() here because the helper calls removedFromDatasource() on the PK instead of the GenericEntity.
+            value.removedFromDatasource();
+            if (doCacheClear) {
+                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_REMOVE, value, false);
+                this.clearCacheLine(value);
+            }
+
 
             if (testMode) {
                 if (removedValue != null) {
@@ -1307,12 +1308,6 @@ public class GenericDelegator implements
             ecaRunner.evalRules(EntityEcaHandler.EV_VALIDATE, EntityEcaHandler.OP_STORE, value, false);
             GenericHelper helper = getEntityHelper(value.getEntityName());
 
-            if (doCacheClear) {
-                // always clear cache before the operation
-                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_STORE, value, false);
-                this.clearCacheLine(value);
-            }
-
             ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_STORE, value, false);
             this.encryptFields(value);
 
@@ -1324,10 +1319,14 @@ public class GenericDelegator implements
             GenericValue updatedEntity = null;
 
             if (testMode) {
-                updatedEntity = this.findOne(value.entityName, value.getPrimaryKey(), false);
+                updatedEntity = this.findOne(value.getEntityName(), value.getPrimaryKey(), false);
             }
 
             int retVal = helper.store(value);
+            if (doCacheClear) {
+                ecaRunner.evalRules(EntityEcaHandler.EV_CACHE_CLEAR, EntityEcaHandler.OP_STORE, value, false);
+                this.clearCacheLine(value);
+            }
 
             if (testMode) {
                 storeForTestRollback(new TestOperation(OperationType.UPDATE, updatedEntity));
@@ -2147,11 +2146,6 @@ public class GenericDelegator implements
      * @see org.ofbiz.entity.Delegator#clearCacheLine(org.ofbiz.entity.GenericValue, boolean)
      */
     public void clearCacheLine(GenericValue value, boolean distribute) {
-        // TODO: make this a bit more intelligent by passing in the operation being done (create, update, remove) so we can not do unnecessary cache clears...
-        // for instance:
-        // on create don't clear by primary cache (and won't clear original values because there won't be any)
-        // on remove don't clear by and for new values, but do for original values
-
         // Debug.logInfo("running clearCacheLine for value: " + value + ", distribute: " + distribute, module);
         if (value == null) return;
 

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericEntity.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericEntity.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericEntity.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericEntity.java Mon May 27 07:38:39 2013
@@ -26,6 +26,7 @@ import java.sql.Blob;
 import java.sql.SQLException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -76,10 +77,10 @@ public class GenericEntity extends Obser
     public static final NullField NULL_FIELD = new NullField();
 
     /** Name of the GenericDelegator, used to re-get the GenericDelegator when deserialized */
-    protected String delegatorName = null;
+    private String delegatorName = null;
 
     /** Reference to an instance of GenericDelegator used to do some basic operations on this entity value. If null various methods in this class will fail. This is automatically set by the GenericDelegator for all GenericValue objects instantiated through it. You may set this manually for objects you instantiate manually, but it is optional. */
-    protected transient Delegator internalDelegator = null;
+    private transient Delegator internalDelegator = null;
 
     /** Contains the fields for this entity. Note that this should always be a
      *  HashMap to allow for two things: non-synchronized reads (synchronized
@@ -88,24 +89,22 @@ public class GenericEntity extends Obser
      *  between desiring to set a value to null and desiring to not modify the
      *  current value on an update.
      */
-    protected Map<String, Object> fields = FastMap.newInstance();
+    private Map<String, Object> fields = new HashMap<String, Object>();
 
     /** Contains the entityName of this entity, necessary for efficiency when creating EJBs */
-    protected String entityName = null;
+    private String entityName = null;
 
     /** Contains the ModelEntity instance that represents the definition of this entity, not to be serialized */
-    protected transient ModelEntity modelEntity = null;
+    private transient ModelEntity modelEntity = null;
 
-    /** Denotes whether or not this entity has been modified, or is known to be out of sync with the persistent record */
-    protected boolean modified = false;
-    protected boolean generateHashCode = true;
-    protected int cachedHashCode = 0;
+    private boolean generateHashCode = true;
+    private int cachedHashCode = 0;
 
     /** Used to specify whether or not this representation of the entity can be changed; generally cleared when this object comes from a cache */
-    protected boolean mutable = true;
+    private boolean mutable = true;
 
     /** This is an internal field used to specify that a value has come from a sync process and that the auto-stamps should not be over-written */
-    protected boolean isFromEntitySync = false;
+    private boolean isFromEntitySync = false;
 
     /** Creates new GenericEntity - Should never be used, prefer the other options. */
     protected GenericEntity() { }
@@ -143,8 +142,16 @@ public class GenericEntity extends Obser
         return newEntity;
     }
 
+    protected void assertIsMutable() {
+        if (!this.mutable) {
+            Debug.logError(new IllegalStateException("This object has been flagged as immutable (unchangeable), probably because it came from an Entity Engine cache. Cannot modify an immutable entity object."), module);
+            throw new IllegalStateException("This object has been flagged as immutable (unchangeable), probably because it came from an Entity Engine cache. Cannot modify an immutable entity object.");
+        }
+    }
+
     /** Creates new GenericEntity */
     protected void init(ModelEntity modelEntity) {
+        assertIsMutable();
         if (modelEntity == null) {
             throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
@@ -159,6 +166,7 @@ public class GenericEntity extends Obser
 
     /** Creates new GenericEntity from existing Map */
     protected void init(Delegator delegator, ModelEntity modelEntity, Map<String, ? extends Object> fields) {
+        assertIsMutable();
         if (modelEntity == null) {
             throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
@@ -176,6 +184,7 @@ public class GenericEntity extends Obser
 
     /** Creates new GenericEntity from existing Map */
     protected void init(Delegator delegator, ModelEntity modelEntity, Object singlePkValue) {
+        assertIsMutable();
         if (modelEntity == null) {
             throw new IllegalArgumentException("Cannot create a GenericEntity with a null modelEntity parameter");
         }
@@ -196,6 +205,7 @@ public class GenericEntity extends Obser
 
     /** Copy Constructor: Creates new GenericEntity from existing GenericEntity */
     protected void init(GenericEntity value) {
+        assertIsMutable();
         // check some things
         if (value.entityName == null) {
             throw new IllegalArgumentException("Cannot create a GenericEntity with a null entityName in the modelEntity parameter");
@@ -209,13 +219,13 @@ public class GenericEntity extends Obser
     }
 
     public void reset() {
+        assertIsMutable();
         // from GenericEntity
         this.delegatorName = null;
         this.internalDelegator = null;
         this.fields = FastMap.newInstance();
         this.entityName = null;
         this.modelEntity = null;
-        this.modified = false;
         this.generateHashCode = true;
         this.cachedHashCode = 0;
         this.mutable = true;
@@ -223,6 +233,7 @@ public class GenericEntity extends Obser
     }
 
     public void refreshFromValue(GenericEntity newValue) throws GenericEntityException {
+        assertIsMutable();
         if (newValue == null) {
             throw new GenericEntityException("Could not refresh value, new value not found for: " + this);
         }
@@ -231,24 +242,40 @@ public class GenericEntity extends Obser
         if (!thisPK.equals(newPK)) {
             throw new GenericEntityException("Could not refresh value, new value did not have the same primary key; this PK=" + thisPK + ", new value PK=" + newPK);
         }
-        this.fields = newValue.fields;
+        this.fields = new HashMap<String, Object>(newValue.fields);
         this.setDelegator(newValue.getDelegator());
         this.generateHashCode = newValue.generateHashCode;
         this.cachedHashCode = newValue.cachedHashCode;
-        this.modified = false;
     }
 
+    /**
+     * 
+     * @deprecated Use hasChanged()
+     */
     public boolean isModified() {
-        return this.modified;
+        return this.hasChanged();
     }
 
+    /**
+     * Flags this object as being synchronized with the data source.
+     * The entity engine will call this method immediately after
+     * populating this object with data from the data source.
+     */
     public void synchronizedWithDatasource() {
-        this.modified = false;
+        assertIsMutable();
+        this.clearChanged();
     }
 
+    /**
+     * Flags this object as being removed from the data source.
+     * The entity engine will call this method immediately after
+     * removing this value from the data source. Once this method is
+     * called, the object is immutable.
+     */
     public void removedFromDatasource() {
-        // seems kind of minimal, but should do for now...
-        this.modified = true;
+        assertIsMutable();
+        this.hasChanged();
+        this.setImmutable();
     }
 
     public boolean isMutable() {
@@ -256,7 +283,10 @@ public class GenericEntity extends Obser
     }
 
     public void setImmutable() {
-        this.mutable = false;
+        if (this.mutable) {
+            this.mutable = false;
+            this.fields = Collections.unmodifiableMap(this.fields);
+        }
     }
 
     /**
@@ -270,6 +300,7 @@ public class GenericEntity extends Obser
      * @param isFromEntitySync The isFromEntitySync to set.
      */
     public void setIsFromEntitySync(boolean isFromEntitySync) {
+        assertIsMutable();
         this.isFromEntitySync = isFromEntitySync;
     }
 
@@ -304,6 +335,7 @@ public class GenericEntity extends Obser
 
     /** Set the GenericDelegator instance that created this value object and that is responsible for it. */
     public void setDelegator(Delegator internalDelegator) {
+        assertIsMutable();
         if (internalDelegator == null) return;
         this.delegatorName = internalDelegator.getDelegatorName();
         this.internalDelegator = internalDelegator;
@@ -382,11 +414,7 @@ public class GenericEntity extends Obser
      * @param setIfNull Specifies whether or not to set the value if it is null
      */
     public synchronized Object set(String name, Object value, boolean setIfNull) {
-        if (!this.mutable) {
-            // comment this out to disable the mutable check
-            throw new IllegalStateException("This object has been flagged as immutable (unchangeable), probably because it came from an Entity Engine cache. Cannot set a value in an immutable entity object.");
-        }
-
+        assertIsMutable();
         ModelField modelField = getModelEntity().getField(name);
         if (modelField == null) {
             throw new IllegalArgumentException("[GenericEntity.set] \"" + name + "\" is not a field of " + entityName + ", must be one of: " + getModelEntity().fieldNameString());
@@ -430,7 +458,6 @@ public class GenericEntity extends Obser
             Object old = fields.put(name, value);
 
             generateHashCode = true;
-            modified = true;
             this.setChanged();
             this.notifyObservers(name);
             return old;
@@ -440,9 +467,12 @@ public class GenericEntity extends Obser
     }
 
     public void dangerousSetNoCheckButFast(ModelField modelField, Object value) {
+        assertIsMutable();
         if (modelField == null) throw new IllegalArgumentException("Cannot set field with a null modelField");
         generateHashCode = true;
         this.fields.put(modelField.getName(), value);
+        this.setChanged();
+        this.notifyObservers(modelField.getName());
     }
 
     public Object dangerousGetNoCheckButFast(ModelField modelField) {
@@ -935,9 +965,7 @@ public class GenericEntity extends Obser
      * @return java.util.Map
      */
     public Map<String, Object> getAllFields() {
-        Map<String, Object> newMap = FastMap.newInstance();
-        newMap.putAll(this.fields);
-        return newMap;
+        return new HashMap<String, Object>(this.fields);
     }
 
     /** Used by clients to specify exactly the fields they are interested in
@@ -1427,7 +1455,9 @@ public class GenericEntity extends Obser
     }
 
     public static class NullGenericEntity extends GenericEntity implements NULL {
-        protected NullGenericEntity() { }
+        protected NullGenericEntity() {
+            this.setImmutable();
+        }
 
         @Override
         public String getEntityName() {

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericValue.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericValue.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericValue.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/GenericValue.java Mon May 27 07:38:39 2013
@@ -23,6 +23,7 @@ package org.ofbiz.entity;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Collections;
 
 import javolution.context.ObjectFactory;
 import javolution.lang.Reusable;
@@ -55,17 +56,9 @@ public class GenericValue extends Generi
         }
     };
 
-    /** Map to cache various related entity collections */
-    public transient Map<String, List<GenericValue>> relatedCache = null;
-
-    /** Map to cache various related cardinality one entity collections */
-    public transient Map<String, GenericValue> relatedOneCache = null;
-
-    /** This Map will contain the original field values from the database iff
-     * this GenericValue came from the database. If it was made manually it will
-     * no have this Map, ie it will be null to not take up memory.
+    /** A Map containing the original field values from the database.
      */
-    protected Map<String, Object> originalDbValues = null;
+    private Map<String, Object> originalDbValues = null;
 
     protected GenericValue() { }
 
@@ -106,19 +99,14 @@ public class GenericValue extends Generi
 
     @Override
     public void reset() {
-        // from GenericEntity
         super.reset();
-
-        // from GenericValue
-        this.relatedCache = null;
-        this.relatedOneCache = null;
         this.originalDbValues = null;
     }
 
     @Override
     public void synchronizedWithDatasource() {
         super.synchronizedWithDatasource();
-        this.copyOriginalDbValues();
+        this.originalDbValues = Collections.unmodifiableMap(getAllFields());
     }
 
     public GenericValue create() throws GenericEntityException {
@@ -147,21 +135,12 @@ public class GenericValue extends Generi
 
     public Object getOriginalDbValue(String name) {
         if (getModelEntity().getField(name) == null) {
-            throw new IllegalArgumentException("[GenericEntity.get] \"" + name + "\" is not a field of " + entityName);
+            throw new IllegalArgumentException("[GenericEntity.get] \"" + name + "\" is not a field of " + getEntityName());
         }
         if (originalDbValues == null) return null;
         return originalDbValues.get(name);
     }
 
-    /** This should only be called by the Entity Engine once a GenericValue has
-     * been read from the database so that we have a copy of the original field
-     * values from the Db.
-     */
-    public void copyOriginalDbValues() {
-        this.originalDbValues = FastMap.newInstance();
-        this.originalDbValues.putAll(this.fields);
-    }
-
     /** Get the named Related Entity for the GenericValue from the persistent store
      *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
      *@return List of GenericValue instances as specified in the relation definition
@@ -255,59 +234,6 @@ public class GenericValue extends Generi
         return this.getRelatedCache(relationName, null, orderBy);
     }
 
-    /** Get the named Related Entity for the GenericValue from the persistent
-     *  store, looking first in a cache associated with this entity which is
-     *  destroyed with this ValueObject when no longer used.
-     *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
-     *@return List of GenericValue instances as specified in the relation definition
-     */
-    public List<GenericValue> getRelatedEmbeddedCache(String relationName) throws GenericEntityException {
-        if (relatedCache == null) relatedCache = FastMap.newInstance();
-        List<GenericValue> col = relatedCache.get(relationName);
-
-        if (col == null) {
-            col = getRelated(relationName);
-            relatedCache.put(relationName, col);
-        }
-        return col;
-    }
-
-    /** Get the named Related Entity for the GenericValue from the persistent
-     *  store, looking first in a cache associated with this entity which is
-     *  destroyed with this ValueObject when no longer used.
-     *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
-     * @param byAndFields the fields that must equal in order to keep; may be null
-     * @param orderBy The fields of the named entity to order the query by; may be null;
-     *      optionally add a " ASC" for ascending or " DESC" for descending
-     *@return List of GenericValue instances as specified in the relation definition
-     */
-    public List<GenericValue> getRelatedEmbeddedCache(String relationName, Map<String, ? extends Object> byAndFields, List<String> orderBy) throws GenericEntityException {
-        List<GenericValue> col = getRelatedEmbeddedCache(relationName);
-
-        if (byAndFields != null) col = EntityUtil.filterByAnd(col, byAndFields);
-        if (UtilValidate.isNotEmpty(orderBy)) col = EntityUtil.orderBy(col, orderBy);
-        return col;
-    }
-
-    public void removeRelatedEmbeddedCache(String relationName) {
-        if (relatedCache == null) return;
-        relatedCache.remove(relationName);
-    }
-
-    public void storeRelatedEmbeddedCache(String relationName, List<GenericValue> col) {
-        if (relatedCache == null) relatedCache = FastMap.newInstance();
-        relatedCache.put(relationName, col);
-    }
-
-    public void storeRelatedEmbeddedCache(String relationName, GenericValue value) {
-        if (relatedCache == null) relatedCache = FastMap.newInstance();
-        relatedCache.put(relationName, UtilMisc.toList(value));
-    }
-
-    public void clearEmbeddedCache() {
-        relatedCache.clear();
-    }
-
     /** Get the named Related Entity for the GenericValue from the persistent store
      *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
      *@return List of GenericValue instances as specified in the relation definition
@@ -325,23 +251,6 @@ public class GenericValue extends Generi
         return this.getDelegator().getRelatedOneCache(relationName, this);
     }
 
-    /** Get the named Related Entity for the GenericValue from the persistent
-     *  store, looking first in a cache associated with this entity which is
-     *  destroyed with this ValueObject when no longer used.
-     *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
-     *@return List of GenericValue instances as specified in the relation definition
-     */
-    public GenericValue getRelatedOneEmbeddedCache(String relationName) throws GenericEntityException {
-        if (relatedOneCache == null) relatedOneCache = FastMap.newInstance();
-        GenericValue value = relatedOneCache.get(relationName);
-
-        if (value == null) {
-            value = getRelatedOne(relationName);
-            if (value != null) relatedOneCache.put(relationName, value);
-        }
-        return value;
-    }
-
     /** Get the named Related Entity for the GenericValue from the persistent store and filter it
      *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
      *@param fields the fields that must equal in order to keep
@@ -361,17 +270,6 @@ public class GenericValue extends Generi
         return EntityUtil.filterByAnd(this.getDelegator().getRelatedCache(relationName, this), fields);
     }
 
-    /** Get the named Related Entity for the GenericValue from the persistent
-     *  store and filter it, looking first in a cache associated with this entity which is
-     *  destroyed with this ValueObject when no longer used.
-     *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
-     *@param fields the fields that must equal in order to keep
-     *@return List of GenericValue instances as specified in the relation definition
-     */
-    public List<GenericValue> getRelatedByAndEmbeddedCache(String relationName, Map<String, ? extends Object> fields) throws GenericEntityException {
-        return EntityUtil.filterByAnd(getRelatedEmbeddedCache(relationName), fields);
-    }
-
     /** Get the named Related Entity for the GenericValue from the persistent store and order it
      *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
      *@param orderBy the order that they should be returned
@@ -391,17 +289,6 @@ public class GenericValue extends Generi
         return EntityUtil.orderBy(this.getDelegator().getRelatedCache(relationName, this), orderBy);
     }
 
-    /** Get the named Related Entity for the GenericValue from the persistent
-     *  store and order it, looking first in a cache associated with this entity which is
-     *  destroyed with this ValueObject when no longer used.
-     *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
-     *@param orderBy the order that they should be returned
-     *@return List of GenericValue instances as specified in the relation definition
-     */
-    public List<GenericValue> getRelatedOrderByEmbeddedCache(String relationName, List<String> orderBy) throws GenericEntityException {
-        return EntityUtil.orderBy(getRelatedEmbeddedCache(relationName), orderBy);
-    }
-
     /** Remove the named Related Entity for the GenericValue from the persistent store
      *@param relationName String containing the relation name which is the combination of relation.title and relation.rel-entity-name as specified in the entity XML definition file
      */

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/AbstractEntityConditionCache.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/AbstractEntityConditionCache.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/AbstractEntityConditionCache.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/AbstractEntityConditionCache.java Mon May 27 07:38:39 2013
@@ -62,6 +62,21 @@ public abstract class AbstractEntityCond
         }
     }
 
+    /**
+     * Removes all condition caches that include the specified entity.
+     */
+    public void remove(GenericEntity entity) {
+        UtilCache.clearCache(getCacheName(entity.getEntityName()));
+        ModelEntity model = entity.getModelEntity();
+        if (model != null) {
+            Iterator<String> it = model.getViewConvertorsIterator();
+            while (it.hasNext()) {
+                String targetEntityName = it.next();
+                UtilCache.clearCache(getCacheName(targetEntityName));
+            }
+        }
+    }
+
     public void remove(String entityName, EntityCondition condition) {
         UtilCache<EntityCondition, Map<K, V>> cache = getCache(entityName);
         if (cache == null) return;

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/Cache.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/Cache.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/Cache.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/Cache.java Mon May 27 07:38:39 2013
@@ -112,16 +112,22 @@ public class Cache {
     public GenericValue remove(GenericEntity entity) {
         if (Debug.verboseOn()) Debug.logVerbose("Cache remove GenericEntity: " + entity, module);
         GenericValue oldEntity = entityCache.remove(entity.getPrimaryKey());
-        entityListCache.storeHook(entity, null);
-        entityObjectCache.storeHook(entity, null);
+        // Workaround because AbstractEntityConditionCache.storeHook doesn't work.
+        entityListCache.remove(entity);
+        entityObjectCache.remove(entity);
+        // entityListCache.storeHook(entity, null);
+        // entityObjectCache.storeHook(entity, null);
         return oldEntity;
     }
 
     public GenericValue remove(GenericPK pk) {
         if (Debug.verboseOn()) Debug.logVerbose("Cache remove GenericPK: " + pk, module);
         GenericValue oldEntity = entityCache.remove(pk);
-        entityListCache.storeHook(pk, null);
-        entityObjectCache.storeHook(pk, null);
+        // Workaround because AbstractEntityConditionCache.storeHook doesn't work.
+        entityListCache.remove(pk);
+        entityObjectCache.remove(pk);
+        // entityListCache.storeHook(pk, null);
+        // entityObjectCache.storeHook(pk, null);
         return oldEntity;
     }
 }

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java Mon May 27 07:38:39 2013
@@ -18,11 +18,14 @@
  *******************************************************************************/
 package org.ofbiz.entity.cache;
 
+import java.util.Iterator;
+
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.cache.UtilCache;
 import org.ofbiz.entity.GenericPK;
 import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.model.ModelEntity;
 
 public class EntityCache extends AbstractCache<GenericPK, GenericValue> {
     public static final String module = EntityCache.class.getName();
@@ -77,6 +80,14 @@ public class EntityCache extends Abstrac
         if (Debug.verboseOn()) Debug.logVerbose("Removing from EntityCache with PK [" + pk + "], will remove from this cache: " + (entityCache == null ? "[No cache found to remove from]" : entityCache.getName()), module);
         if (entityCache == null) return null;
         GenericValue retVal = entityCache.remove(pk);
+        ModelEntity model = pk.getModelEntity();
+        if (model != null) {
+            Iterator<String> it = model.getViewConvertorsIterator();
+            while (it.hasNext()) {
+                String targetEntityName = it.next();
+                UtilCache.clearCache(getCacheName(targetEntityName));
+            }
+        }
         if (Debug.verboseOn()) Debug.logVerbose("Removing from EntityCache with PK [" + pk + "], found this in the cache: " + retVal, module);
         return retVal;
     }

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityListCache.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityListCache.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityListCache.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/cache/EntityListCache.java Mon May 27 07:38:39 2013
@@ -22,8 +22,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import org.ofbiz.base.util.Debug;
 import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.model.ModelEntity;
 import org.ofbiz.entity.util.EntityUtil;
 
 public class EntityListCache extends AbstractEntityConditionCache<Object, List<GenericValue>> {
@@ -63,7 +65,16 @@ public class EntityListCache extends Abs
     }
 
     public List<GenericValue> put(String entityName, EntityCondition condition, List<String> orderBy, List<GenericValue> entities) {
-        return super.put(entityName, getFrozenConditionKey(condition), getOrderByKey(orderBy), entities);
+        ModelEntity entity = this.getDelegator().getModelEntity(entityName);
+        if (entity.getNeverCache()) {
+            Debug.logWarning("Tried to put a value of the " + entityName + " entity in the cache but this entity has never-cache set to true, not caching.", module);
+            return null;
+        }
+        for (GenericValue memberValue : entities) {
+            memberValue.setImmutable();
+        }
+        Map<Object, List<GenericValue>> conditionCache = getOrCreateConditionCache(entityName, getFrozenConditionKey(condition));
+        return conditionCache.put(getOrderByKey(orderBy), entities);
     }
 
     public List<GenericValue> remove(String entityName, EntityCondition condition, List<String> orderBy) {

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/eca/EntityEcaHandler.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/eca/EntityEcaHandler.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/eca/EntityEcaHandler.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/eca/EntityEcaHandler.java Mon May 27 07:38:39 2013
@@ -34,6 +34,9 @@ public interface EntityEcaHandler<T> {
     public static final String EV_VALIDATE = "validate";
     public static final String EV_RUN = "run";
     public static final String EV_RETURN = "return";
+    /**
+     * Invoked after the entity operation, but before the cache is cleared.
+     */
     public static final String EV_CACHE_CLEAR = "cache-clear";
     public static final String EV_CACHE_CHECK = "cache-check";
     public static final String EV_CACHE_PUT = "cache-put";

Modified: ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java (original)
+++ ofbiz/branches/release11.04/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java Mon May 27 07:38:39 2013
@@ -104,6 +104,105 @@ public class EntityTestSuite extends Ent
         assertEquals("Retrieved value has the correct description", "New Testing Type #1", testValue.getString("description"));
     }
 
+    public void testRemoveValue() throws Exception {
+        // Retrieve a sample GenericValue, make sure it's correct
+        GenericValue testValue = delegator.findOne("TestingType", false, "testingTypeId", "TEST-4");
+        assertEquals("Retrieved value has the correct description", "Testing Type #4", testValue.getString("description"));
+        testValue.remove();
+        // Test immutable
+        try {
+            testValue.put("description", "New Testing Type #4");
+            fail("Modified an immutable GenericValue");
+        } catch (IllegalStateException e) {
+        }
+        try {
+            testValue.remove("description");
+            fail("Modified an immutable GenericValue");
+        } catch (UnsupportedOperationException e) {
+        }
+        testValue = delegator.findOne("TestingType", false, "testingTypeId", "TEST-4");
+        assertEquals("Finding removed value returns null", null, testValue);
+    }
+
+    /*
+     * Tests the entity cache
+     */
+    public void testEntityCache() throws Exception {
+        // Test primary key cache
+        GenericValue testValue = delegator.findOne("TestingType", true, "testingTypeId", "TEST-3");
+        assertEquals("Retrieved from cache value has the correct description", "Testing Type #3", testValue.getString("description"));
+        // Test immutable
+        try {
+            testValue.put("description", "New Testing Type #3");
+            fail("Modified an immutable GenericValue");
+        } catch (IllegalStateException e) {
+        }
+        try {
+            testValue.remove("description");
+            fail("Modified an immutable GenericValue");
+        } catch (UnsupportedOperationException e) {
+        }
+        // Test entity value update operation updates the cache
+        testValue = (GenericValue) testValue.clone();
+        testValue.put("description", "New Testing Type #3");
+        testValue.store();
+        testValue = delegator.findOne("TestingType", true, "testingTypeId", "TEST-3");
+        assertEquals("Retrieved from cache value has the correct description", "New Testing Type #3", testValue.getString("description"));
+        // Test entity value remove operation updates the cache
+        testValue = (GenericValue) testValue.clone();
+        testValue.remove();
+        testValue = delegator.findOne("TestingType", true, "testingTypeId", "TEST-3");
+        assertEquals("Retrieved from cache value is null", null, testValue);
+        // Test entity condition cache
+        EntityCondition testCondition = EntityCondition.makeCondition("description", EntityOperator.EQUALS, "Testing Type #2");
+        List<GenericValue> testList = delegator.findList("TestingType", testCondition, null, null, null, true);
+        assertEquals("Delegator findList returned one value", 1, testList.size());
+        testValue = testList.get(0);
+        assertEquals("Retrieved from cache value has the correct description", "Testing Type #2", testValue.getString("description"));
+        // Test immutable
+        try {
+            testValue.put("description", "New Testing Type #2");
+            fail("Modified an immutable GenericValue");
+        } catch (IllegalStateException e) {
+        }
+        try {
+            testValue.remove("description");
+            fail("Modified an immutable GenericValue");
+        } catch (UnsupportedOperationException e) {
+        }
+        // Test entity value create operation updates the cache
+        testValue = (GenericValue) testValue.clone();
+        testValue.put("testingTypeId", "TEST-9");
+        testValue.create();
+        testList = delegator.findList("TestingType", testCondition, null, null, null, true);
+        assertEquals("Delegator findList returned two values", 2, testList.size());
+        // Test entity value update operation updates the cache
+        testValue.put("description", "New Testing Type #2");
+        testValue.store();
+        testList = delegator.findList("TestingType", testCondition, null, null, null, true);
+        assertEquals("Delegator findList returned one value", 1, testList.size());
+        // Test entity value remove operation updates the cache
+        testValue = testList.get(0);
+        testValue = (GenericValue) testValue.clone();
+        testValue.remove();
+        testList = delegator.findList("TestingType", testCondition, null, null, null, true);
+        assertEquals("Delegator findList returned empty list", 0, testList.size());
+        // Test view entities in the pk cache - updating an entity should clear pk caches for all view entities containing that entity.
+        testValue = delegator.create("TestingSubtype", "testingTypeId", "TEST-9", "subtypeDescription", "Testing Subtype #9");
+        assertNotNull("TestingSubtype created", testValue);
+        // Confirm member entity appears in the view
+        testValue = delegator.findOne("TestingViewPks", true, "testingTypeId", "TEST-9");
+        assertEquals("View retrieved from cache has the correct member description", "Testing Subtype #9", testValue.getString("subtypeDescription"));
+        testValue = delegator.findOne("TestingSubtype", true, "testingTypeId", "TEST-9");
+        // Modify member entity
+        testValue = (GenericValue) testValue.clone();
+        testValue.put("subtypeDescription", "New Testing Subtype #9");
+        testValue.store();
+        // Check if cached view contains the modification
+        testValue = delegator.findOne("TestingViewPks", true, "testingTypeId", "TEST-9");
+        assertEquals("View retrieved from cache has the correct member description", "New Testing Subtype #9", testValue.getString("subtypeDescription"));
+    }
+
     /*
      * Tests XML serialization by serializing/deserializing a GenericValue
      */

Modified: ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelForm.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelForm.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelForm.java (original)
+++ ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelForm.java Mon May 27 07:38:39 2013
@@ -21,6 +21,7 @@ package org.ofbiz.widget.form;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -46,6 +47,7 @@ import org.ofbiz.base.util.UtilXml;
 import org.ofbiz.base.util.collections.FlexibleMapAccessor;
 import org.ofbiz.base.util.collections.MapStack;
 import org.ofbiz.base.util.string.FlexibleStringExpander;
+import org.ofbiz.entity.GenericEntity;
 import org.ofbiz.entity.GenericEntityException;
 import org.ofbiz.entity.model.ModelEntity;
 import org.ofbiz.entity.model.ModelField;
@@ -1484,7 +1486,13 @@ public class ModelForm extends ModelWidg
                 if (UtilValidate.isNotEmpty(this.getListEntryName())) {
                     localContext.put(this.getListEntryName(), item);
                 } else {
-                    localContext.push(itemMap);
+                    if (itemMap instanceof GenericEntity) {
+                        // Rendering code might try to modify the GenericEntity instance,
+                        // so we make a copy of it.
+                        localContext.push(new HashMap<String, Object>(itemMap));
+                    } else {
+                        localContext.push(itemMap);
+                    }
                 }
 
                 // reset/remove the BshInterpreter now as well as later because chances are there is an interpreter at this level of the stack too

Modified: ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelFormField.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelFormField.java?rev=1486529&r1=1486528&r2=1486529&view=diff
==============================================================================
--- ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelFormField.java (original)
+++ ofbiz/branches/release11.04/framework/widget/src/org/ofbiz/widget/form/ModelFormField.java Mon May 27 07:38:39 2013
@@ -1781,7 +1781,9 @@ public class ModelFormField {
                 for (GenericValue value: values) {
                     // add key and description with string expansion, ie expanding ${} stuff, passing locale explicitly to expand value string because it won't be found in the Entity
                     MapStack<String> localContext = MapStack.create(context);
-                    localContext.push(value);
+                    // Rendering code might try to modify the GenericEntity instance,
+                    // so we make a copy of it.
+                    localContext.push(new HashMap<String, Object>(value));
 
                     // expand with the new localContext, which is locale aware
                     String optionDesc = this.description.expandString(localContext, locale);
@@ -2348,7 +2350,9 @@ public class ModelFormField {
             if (value != null) {
                 // expanding ${} stuff, passing locale explicitly to expand value string because it won't be found in the Entity
                 MapStack<String> localContext = MapStack.create(context);
-                localContext.push(value);
+                // Rendering code might try to modify the GenericEntity instance,
+                // so we make a copy of it.
+                localContext.push(new HashMap<String, Object>(value));
 
                 // expand with the new localContext, which is locale aware
                 retVal = this.description.expandString(localContext, locale);