You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ad...@apache.org on 2013/04/28 22:10:03 UTC

svn commit: r1476826 - in /ofbiz/trunk/framework/entity/src/org/ofbiz/entity: model/ModelEntity.java model/ModelReader.java model/ModelViewEntity.java test/EntityTestSuite.java

Author: adrianc
Date: Sun Apr 28 20:10:02 2013
New Revision: 1476826

URL: http://svn.apache.org/r1476826
Log:
A small overhaul for the ModelEntity class. The class is not thread-safe. Worse, there were four ModelField-related collections that were not updated consistently - causing invalid data. Two of the collections (field list and field map) were redundant, so I eliminated one.

This is a step toward thread-safety, but the class still isn't thread-safe. It would be best if this class was immutable, but that will require a lot of re-engineering.


Modified:
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelReader.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelViewEntity.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java?rev=1476826&r1=1476825&r2=1476826&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java Sun Apr 28 20:10:02 2013
@@ -20,6 +20,7 @@ package org.ofbiz.entity.model;
 
 import java.io.PrintWriter;
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -32,6 +33,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.GeneralException;
@@ -68,7 +70,7 @@ public class ModelEntity extends ModelIn
     public static final String CREATE_STAMP_TX_FIELD = "createdTxStamp";
 
     /** The ModelReader that created this Entity */
-    protected ModelReader modelReader = null;
+    private final ModelReader modelReader;
 
     /** The entity-name of the Entity */
     protected String entityName = "";
@@ -85,22 +87,23 @@ public class ModelEntity extends ModelIn
     /** The sequence-bank-size of the Entity */
     protected Integer sequenceBankSize = null;
 
-    /** A List of the Field objects for the Entity */
-    protected List<ModelField> fields = new LinkedList<ModelField>();
+    /** Synchronization object used to control access to the ModelField collection objects.
+     * A single lock is used for all ModelField collections so collection updates are atomic. */
+    private final Object fieldsLock = new Object();
 
-    protected Map<String, ModelField> fieldsMap = null;
+    private final Map<String, ModelField> fieldsMap = new HashMap<String, ModelField>();
 
     /** A List of the Field objects for the Entity, one for each Primary Key */
-    protected List<ModelField> pks = new LinkedList<ModelField>();
+    private final ArrayList<ModelField> pks = new ArrayList<ModelField>();
 
     /** A List of the Field objects for the Entity, one for each NON Primary Key */
-    protected List<ModelField> nopks = new LinkedList<ModelField>();
+    private final ArrayList<ModelField> nopks = new ArrayList<ModelField>();
 
     /** relations defining relationships between this entity and other entities */
-    protected List<ModelRelation> relations = new LinkedList<ModelRelation>();
+    protected CopyOnWriteArrayList<ModelRelation> relations = new CopyOnWriteArrayList<ModelRelation>();
 
     /** indexes on fields/columns in this entity */
-    protected List<ModelIndex> indexes = new LinkedList<ModelIndex>();
+    private CopyOnWriteArrayList<ModelIndex> indexes = new CopyOnWriteArrayList<ModelIndex>();
 
     /** The reference of the dependentOn entity model */
     protected ModelEntity specializationOfModelEntity = null;
@@ -109,7 +112,7 @@ public class ModelEntity extends ModelIn
     protected Map<String, ModelEntity> specializedEntities = new HashMap<String, ModelEntity>();
 
     /** map of ModelViewEntities that references this model */
-    protected Set<String> viewEntities = new HashSet<String>();
+    private final Set<String> viewEntities = new HashSet<String>();
 
     /** An indicator to specify if this entity requires locking for updates */
     protected boolean doLock = false;
@@ -127,14 +130,14 @@ public class ModelEntity extends ModelIn
 
     protected boolean autoClearCache = true;
 
-    protected Boolean hasFieldWithAuditLog = null;
-
     /** The location of this entity's definition */
     protected String location = "";
 
     // ===== CONSTRUCTORS =====
     /** Default Constructor */
-    public ModelEntity() {}
+    public ModelEntity() {
+        this.modelReader = null;
+    }
 
     protected ModelEntity(ModelReader reader) {
         this.modelReader = reader;
@@ -154,32 +157,30 @@ public class ModelEntity extends ModelIn
     /** XML Constructor */
     public ModelEntity(ModelReader reader, Element entityElement, UtilTimer utilTimer, ModelInfo def) {
         this(reader, entityElement, def);
-
         if (utilTimer != null) utilTimer.timerString("  createModelEntity: before general/basic info");
         this.populateBasicInfo(entityElement);
-
+        if (utilTimer != null) utilTimer.timerString("  createModelEntity: before prim-keys");
+        List<String> pkFieldNames = new ArrayList<String>();
+        for (Element pkElement: UtilXml.childElementList(entityElement, "prim-key")) {
+            pkFieldNames.add(pkElement.getAttribute("field").intern());
+        }
         if (utilTimer != null) utilTimer.timerString("  createModelEntity: before fields");
         for (Element fieldElement: UtilXml.childElementList(entityElement, "field")) {
             ModelField field = reader.createModelField(fieldElement);
             if (field != null) {
-                field.setModelEntity(this);
-                this.fields.add(field);
+                internalAddField(field, pkFieldNames);
             }
         }
-
         // if applicable automatically add the STAMP_FIELD and STAMP_TX_FIELD fields
-        if ((this.doLock || !this.noAutoStamp) && !this.isField(STAMP_FIELD)) {
+        if ((this.doLock || !this.noAutoStamp) && !fieldsMap.containsKey(STAMP_FIELD)) {
             ModelField newField = reader.createModelField(STAMP_FIELD, "date-time", null, false);
             newField.setIsAutoCreatedInternal(true);
-            newField.setModelEntity(this);
-            this.fields.add(newField);
+            internalAddField(newField, pkFieldNames);
         }
-        if (!this.noAutoStamp && !this.isField(STAMP_TX_FIELD)) {
+        if (!this.noAutoStamp && !fieldsMap.containsKey(STAMP_TX_FIELD)) {
             ModelField newField = reader.createModelField(STAMP_TX_FIELD, "date-time", null, false);
             newField.setIsAutoCreatedInternal(true);
-            newField.setModelEntity(this);
-            this.fields.add(newField);
-
+            internalAddField(newField, pkFieldNames);
             // also add an index for this field
             String indexName = ModelUtil.shortenDbName(this.tableName + "_TXSTMP", 18);
             ModelIndex txIndex = new ModelIndex(this, indexName, false);
@@ -187,20 +188,16 @@ public class ModelEntity extends ModelIn
             txIndex.setModelEntity(this);
             indexes.add(txIndex);
         }
-
         // if applicable automatically add the CREATE_STAMP_FIELD and CREATE_STAMP_TX_FIELD fields
-        if ((this.doLock || !this.noAutoStamp) && !this.isField(CREATE_STAMP_FIELD)) {
+        if ((this.doLock || !this.noAutoStamp) && !fieldsMap.containsKey(CREATE_STAMP_FIELD)) {
             ModelField newField = reader.createModelField(CREATE_STAMP_FIELD, "date-time", null, false);
             newField.setIsAutoCreatedInternal(true);
-            newField.setModelEntity(this);
-            this.fields.add(newField);
+            internalAddField(newField, pkFieldNames);
         }
-        if (!this.noAutoStamp && !this.isField(CREATE_STAMP_TX_FIELD)) {
+        if (!this.noAutoStamp && !fieldsMap.containsKey(CREATE_STAMP_TX_FIELD)) {
             ModelField newField = reader.createModelField(CREATE_STAMP_TX_FIELD, "date-time", null, false);
             newField.setIsAutoCreatedInternal(true);
-            newField.setModelEntity(this);
-            this.fields.add(newField);
-
+            internalAddField(newField, pkFieldNames);
             // also add an index for this field
             String indexName = ModelUtil.shortenDbName(this.tableName + "_TXCRTS", 18);
             ModelIndex txIndex = new ModelIndex(this, indexName, false);
@@ -208,26 +205,17 @@ public class ModelEntity extends ModelIn
             txIndex.setModelEntity(this);
             indexes.add(txIndex);
         }
-
-        if (utilTimer != null) utilTimer.timerString("  createModelEntity: before prim-keys");
-        for (Element pkElement: UtilXml.childElementList(entityElement, "prim-key")) {
-            ModelField field = reader.findModelField(this, pkElement.getAttribute("field").intern());
-            if (field != null) {
-                this.pks.add(field);
-                field.isPk = true;
-                field.isNotNull = true;
+        // Must be done last to preserve pk field sequence
+        for (String pkFieldName : pkFieldNames) {
+            ModelField pkField = fieldsMap.get(pkFieldName);
+            if (pkField == null) {
+                Debug.logWarning("Error in entity definition - primary key is invalid for entity " + this.getEntityName(), module);
             } else {
-                Debug.logError("[ModelReader.createModelEntity] ERROR: Could not find field \"" +
-                        pkElement.getAttribute("field") + "\" specified in a prim-key", module);
+                pks.add(pkField);
             }
         }
-
-        // now that we have the pks and the fields, make the nopks List
-        this.nopks = new LinkedList<ModelField>();
-        for (ModelField field: this.fields) {
-            if (!field.isPk) this.nopks.add(field);
-        }
-
+        pks.trimToSize();
+        nopks.trimToSize();
         if (utilTimer != null) utilTimer.timerString("  createModelEntity: before relations");
         this.populateRelated(reader, entityElement);
         this.populateIndexes(entityElement);
@@ -236,6 +224,7 @@ public class ModelEntity extends ModelIn
     /** DB Names Constructor */
     public ModelEntity(String tableName, Map<String, DatabaseUtil.ColumnCheckInfo> colMap, ModelFieldTypeReader modelFieldTypeReader, boolean isCaseSensitive) {
         // if there is a dot in the name, remove it and everything before it, should be the schema name
+        this.modelReader = null;
         this.tableName = tableName;
         int dotIndex = this.tableName.indexOf(".");
         if (dotIndex >= 0) {
@@ -245,9 +234,8 @@ public class ModelEntity extends ModelIn
         for (Map.Entry<String, DatabaseUtil.ColumnCheckInfo> columnEntry: colMap.entrySet()) {
             DatabaseUtil.ColumnCheckInfo ccInfo = columnEntry.getValue();
             ModelField newField = new ModelField(ccInfo, modelFieldTypeReader);
-            this.fields.add(newField);
+            addField(newField);
         }
-        this.updatePkLists();
     }
 
     protected void populateBasicInfo(Element entityElement) {
@@ -271,24 +259,38 @@ public class ModelEntity extends ModelIn
         }
     }
 
+    private void internalAddField(ModelField newField, List<String> pkFieldNames) {
+        if (pkFieldNames.contains(newField.getName())) {
+            newField.setIsPk(true);
+            // Constructor will add to pk list
+        } else {
+            this.nopks.add(newField);
+        }
+        newField.setModelEntity(this);
+        this.fieldsMap.put(newField.getName(), newField);
+    }
 
     protected void populateRelated(ModelReader reader, Element entityElement) {
+        List<ModelRelation> tempList = new ArrayList<ModelRelation>(this.relations);
         for (Element relationElement: UtilXml.childElementList(entityElement, "relation")) {
             ModelRelation relation = reader.createRelation(this, relationElement);
             if (relation != null) {
                 relation.setModelEntity(this);
-                this.relations.add(relation);
+                tempList.add(relation);
             }
         }
+        this.relations = new CopyOnWriteArrayList<ModelRelation>(tempList);
     }
 
 
     protected void populateIndexes(Element entityElement) {
+        List<ModelIndex> tempList = new ArrayList<ModelIndex>(this.indexes);
         for (Element indexElement: UtilXml.childElementList(entityElement, "index")) {
             ModelIndex index = new ModelIndex(this, indexElement);
             index.setModelEntity(this);
-            this.indexes.add(index);
+            tempList.add(index);
         }
+        this.indexes = new CopyOnWriteArrayList<ModelIndex>(tempList);
     }
 
     public boolean containsAllPkFieldNames(Set<String> fieldNames) {
@@ -353,9 +355,12 @@ public class ModelEntity extends ModelIn
                 } else {
                     // add to the entity as a new field
                     field.setModelEntity(this);
-                    this.fields.add(field);
-                    // this will always be true for now as extend-entity fielsd are always nonpks
-                    if (!field.isPk) this.nopks.add(field);
+                    synchronized (fieldsLock) {
+                        this.fieldsMap.put(field.getName(), field);
+                        // this will always be true for now as extend-entity fields are always nonpks
+                        if (!field.isPk)
+                            this.nopks.add(field);
+                    }
                 }
             }
         }
@@ -463,17 +468,12 @@ public class ModelEntity extends ModelIn
     }
 
     public boolean getHasFieldWithAuditLog() {
-        if (this.hasFieldWithAuditLog == null) {
-            this.hasFieldWithAuditLog = false;
-            for (ModelField mf: this.fields) {
-                if (mf.getEnableAuditLog()) {
-                    this.hasFieldWithAuditLog = true;
-                }
+        for (ModelField mf : getFieldsUnmodifiable()) {
+            if (mf.getEnableAuditLog()) {
+                return true;
             }
-            return this.hasFieldWithAuditLog;
-        } else {
-            return this.hasFieldWithAuditLog;
         }
+        return false;
     }
 
     /* Get the location of this entity's definition */
@@ -508,24 +508,11 @@ public class ModelEntity extends ModelIn
         return this.sequenceBankSize;
     }
 
-    public void updatePkLists() {
-        pks = new LinkedList<ModelField>();
-        nopks = new LinkedList<ModelField>();
-
-        for (ModelField field: fields) {
-            if (field.isPk)
-                pks.add(field);
-            else
-                nopks.add(field);
-        }
-    }
-
     public boolean isField(String fieldName) {
         if (fieldName == null) return false;
-        for (ModelField field: fields) {
-            if (field.name.equals(fieldName)) return true;
+        synchronized (fieldsLock) {
+            return fieldsMap.containsKey(fieldName);
         }
-        return false;
     }
 
     public boolean areFields(Collection<String> fieldNames) {
@@ -537,23 +524,34 @@ public class ModelEntity extends ModelIn
     }
 
     public int getPksSize() {
-        return this.pks.size();
+        synchronized (fieldsLock) {
+            return this.pks.size();
+        }
     }
 
     public ModelField getOnlyPk() {
-        if (this.pks.size() == 1) {
-            return this.pks.get(0);
-        } else {
-            throw new IllegalArgumentException("Error in getOnlyPk, the [" + this.getEntityName() + "] entity has more than one pk!");
+        synchronized (fieldsLock) {
+            if (this.pks.size() == 1) {
+                return this.pks.get(0);
+            } else {
+                throw new IllegalArgumentException("Error in getOnlyPk, the [" + this.getEntityName() + "] entity has more than one pk!");
+            }
         }
     }
 
     public Iterator<ModelField> getPksIterator() {
-        return this.pks.iterator();
+        return getPkFields().iterator();
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<ModelField> getPkFields() {
+        synchronized (fieldsLock) {
+            return (List) this.pks.clone();
+        }
     }
 
     public List<ModelField> getPkFieldsUnmodifiable() {
-        return Collections.unmodifiableList(this.pks);
+        return Collections.unmodifiableList(getPkFields());
     }
 
     public String getFirstPkFieldName() {
@@ -566,29 +564,41 @@ public class ModelEntity extends ModelIn
     }
 
     public int getNopksSize() {
-        return this.nopks.size();
+        synchronized (fieldsLock) {
+            return this.nopks.size();
+        }
     }
 
     public Iterator<ModelField> getNopksIterator() {
-        return this.nopks.iterator();
+        return getNopksCopy().iterator();
     }
 
     public List<ModelField> getNopksCopy() {
-        List<ModelField> newList = new LinkedList<ModelField>();
-        newList.addAll(this.nopks);
-        return newList;
+        synchronized (fieldsLock) {
+            return new ArrayList<ModelField>(this.nopks);
+        }
     }
 
     public int getFieldsSize() {
-        return this.fields.size();
+        synchronized (fieldsLock) {
+            return this.fieldsMap.size();
+        }
     }
 
     public Iterator<ModelField> getFieldsIterator() {
-        return this.fields.iterator();
+        synchronized (fieldsLock) {
+            List<ModelField> newList = new ArrayList<ModelField>(fieldsMap.size());
+            newList.addAll(this.fieldsMap.values());
+            return newList.iterator();
+        }
     }
 
     public List<ModelField> getFieldsUnmodifiable() {
-        return Collections.unmodifiableList(this.fields);
+        synchronized (fieldsLock) {
+            List<ModelField> newList = new ArrayList<ModelField>(fieldsMap.size());
+            newList.addAll(this.fieldsMap.values());
+            return Collections.unmodifiableList(newList);
+        }
     }
 
     /** The col-name of the Field, the alias of the field if this is on a view-entity */
@@ -600,95 +610,59 @@ public class ModelEntity extends ModelIn
 
     public ModelField getField(String fieldName) {
         if (fieldName == null) return null;
-        if (fieldsMap == null) {
-            createFieldsMap();
+        synchronized (fieldsLock) {
+            return fieldsMap.get(fieldName);
         }
-        ModelField modelField = fieldsMap.get(fieldName);
-        if (modelField == null) {
-            // sometimes weird things happen and this getField method is called before the fields are all populated, so before moving on just re-create the fieldsMap again real quick...
-            // the purpose of the fieldsMap is for speed, but if failures are a little slower, no biggie
-            createFieldsMap();
-            modelField = fieldsMap.get(fieldName);
-        }
-        return modelField;
-    }
-
-    protected synchronized void createFieldsMap() {
-        Map<String, ModelField> tempMap = new HashMap<String, ModelField>();
-        for (int i = 0; i < fields.size(); i++) {
-            ModelField field = fields.get(i);
-            tempMap.put(field.name, field);
-        }
-        fieldsMap = tempMap;
     }
 
     public void addField(ModelField field) {
-        if (field == null) return;
+        if (field == null)
+            return;
         field.setModelEntity(this);
-        this.fields.add(field);
-
-        if (field.isPk) {
-            pks.add(field);
-        } else {
-            nopks.add(field);
-        }
-    }
-
-    public ModelField removeField(int index) {
-        ModelField field = null;
-
-        field = fields.remove(index);
-        if (field == null) return null;
-
-        if (field.isPk) {
-            pks.remove(field);
-        } else {
-            nopks.remove(field);
+        synchronized (fieldsLock) {
+            fieldsMap.put(field.getName(), field);
+            if (field.isPk) {
+                pks.add(field);
+            } else {
+                nopks.add(field);
+            }
         }
-        return field;
     }
 
     public ModelField removeField(String fieldName) {
-        if (fieldName == null) return null;
-        ModelField field = null;
-
-        // FIXME: when the field is removed, i is still incremented
-        // while not correct, this doesn't cause any problems
-        for (int i = 0; i < fields.size(); i++) {
-            field = fields.get(i);
-            if (field.name.equals(fieldName)) {
-                fields.remove(i);
+        if (fieldName == null)
+            return null;
+        synchronized (fieldsLock) {
+            ModelField field = fieldsMap.remove(fieldName);
+            if (field != null) {
                 if (field.isPk) {
                     pks.remove(field);
                 } else {
                     nopks.remove(field);
                 }
             }
-            field = null;
+            return field;
         }
-        return field;
     }
 
     public List<String> getAllFieldNames() {
-        return getFieldNamesFromFieldVector(fields);
+        synchronized (fieldsLock) {
+            List<String> newList = new ArrayList<String>(fieldsMap.size());
+            newList.addAll(this.fieldsMap.keySet());
+            return newList;
+        }
     }
 
     public List<String> getPkFieldNames() {
-        return getFieldNamesFromFieldVector(pks);
+        return getFieldNamesFromFieldVector(getPkFields());
     }
 
     public List<String> getNoPkFieldNames() {
-        return getFieldNamesFromFieldVector(nopks);
-    }
-
-    public List<String> getFieldNamesFromFieldVector(ModelField... modelFields) {
-        return getFieldNamesFromFieldVector(Arrays.asList(modelFields));
+        return getFieldNamesFromFieldVector(getNopksCopy());
     }
 
-    public List<String> getFieldNamesFromFieldVector(List<ModelField> modelFields) {
-        List<String> nameList = new LinkedList<String>();
-
-        if (modelFields == null || modelFields.size() <= 0) return nameList;
+    private List<String> getFieldNamesFromFieldVector(List<ModelField> modelFields) {
+        List<String> nameList = new ArrayList<String>(modelFields.size());
         for (ModelField field: modelFields) {
             nameList.add(field.name);
         }
@@ -804,15 +778,21 @@ public class ModelEntity extends ModelIn
     }
 
     public int getViewEntitiesSize() {
-        return this.viewEntities.size();
+        synchronized (viewEntities) {
+            return this.viewEntities.size();
+        }
     }
 
     public Iterator<String> getViewConvertorsIterator() {
-        return this.viewEntities.iterator();
+        synchronized (viewEntities) {
+            return new HashSet<String>(this.viewEntities).iterator();
+        }
     }
 
     public void addViewEntity(ModelViewEntity view) {
-        this.viewEntities.add(view.getEntityName());
+        synchronized (viewEntities) {
+            this.viewEntities.add(view.getEntityName());
+        }
     }
 
     public List<? extends Map<String, Object>> convertToViewValues(String viewEntityName, GenericEntity entity) {
@@ -822,7 +802,9 @@ public class ModelEntity extends ModelIn
     }
 
     public boolean removeViewEntity(String viewEntityName) {
-        return this.viewEntities.remove(viewEntityName);
+        synchronized (viewEntities) {
+            return this.viewEntities.remove(viewEntityName);
+        }
     }
 
     public boolean removeViewEntity(ModelViewEntity viewEntity) {
@@ -883,15 +865,15 @@ public class ModelEntity extends ModelIn
     }
 
     public String fieldNameString(String separator, String afterLast) {
-        return nameString(fields, separator, afterLast);
+        return nameString(getFieldsUnmodifiable(), separator, afterLast);
     }
 
     public String fieldTypeNameString() {
-        return typeNameString(fields);
+        return typeNameString(getFieldsUnmodifiable());
     }
 
     public String primKeyClassNameString() {
-        return typeNameString(pks);
+        return typeNameString(getPkFields());
     }
 
     public String pkNameString() {
@@ -899,11 +881,11 @@ public class ModelEntity extends ModelIn
     }
 
     public String pkNameString(String separator, String afterLast) {
-        return nameString(pks, separator, afterLast);
+        return nameString(getPkFields(), separator, afterLast);
     }
 
     public String nonPkNullList() {
-        return fieldsStringList(fields, "null", ", ", false, true);
+        return fieldsStringList(getFieldsUnmodifiable(), "null", ", ", false, true);
     }
 
     @Deprecated
@@ -1577,7 +1559,7 @@ public class ModelEntity extends ModelIn
         // for classProperties add field names AND relationship names to get a nice, complete chart
         List<String> classPropertiesList = new LinkedList<String>();
         topLevelMap.put("classProperties", classPropertiesList);
-        for (ModelField field: this.fields) {
+        for (ModelField field: this.fieldsMap.values()) {
             if (field.getIsAutoCreatedInternal()) continue;
             if (field.getIsPk()) {
                 classPropertiesList.add(field.getName() + "*");
@@ -1595,7 +1577,7 @@ public class ModelEntity extends ModelIn
         // attributes
         List<Map<String, Object>> attributesList = new LinkedList<Map<String, Object>>();
         topLevelMap.put("attributes", attributesList);
-        for (ModelField field: this.fields) {
+        for (ModelField field: this.fieldsMap.values()) {
             if (field.getIsAutoCreatedInternal()) continue;
 
             ModelFieldType fieldType = modelFieldTypeReader.getModelFieldType(field.getType());
@@ -1632,7 +1614,7 @@ public class ModelEntity extends ModelIn
         // primaryKeyAttributes
         List<String> primaryKeyAttributesList = new LinkedList<String>();
         topLevelMap.put("primaryKeyAttributes", primaryKeyAttributesList);
-        for (ModelField pkField: this.pks) {
+        for (ModelField pkField : getPkFields()) {
             primaryKeyAttributesList.add(pkField.getName());
         }
 

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelReader.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelReader.java?rev=1476826&r1=1476825&r2=1476826&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelReader.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelReader.java Sun Apr 28 20:10:02 2013
@@ -607,7 +607,7 @@ TEMP_VIEW_LOOP:
     }
 
     public ModelField findModelField(ModelEntity entity, String fieldName) {
-        for (ModelField field: entity.fields) {
+        for (ModelField field: entity.getFieldsUnmodifiable()) {
             if (field.name.compareTo(fieldName) == 0) {
                 return field;
             }

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelViewEntity.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelViewEntity.java?rev=1476826&r1=1476825&r2=1476826&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelViewEntity.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelViewEntity.java Sun Apr 28 20:10:02 2013
@@ -489,12 +489,7 @@ public class ModelViewEntity extends Mod
                 }
             }
 
-            this.fields.add(field);
-            if (field.isPk) {
-                this.pks.add(field);
-            } else {
-                this.nopks.add(field);
-            }
+            addField(field);
 
             if ("count".equals(alias.function) || "count-distinct".equals(alias.function)) {
                 // if we have a "count" function we have to change the type

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java?rev=1476826&r1=1476825&r2=1476826&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java Sun Apr 28 20:10:02 2013
@@ -46,6 +46,8 @@ import org.ofbiz.entity.condition.Entity
 import org.ofbiz.entity.condition.EntityOperator;
 import org.ofbiz.entity.config.DatasourceInfo;
 import org.ofbiz.entity.config.EntityConfigUtil;
+import org.ofbiz.entity.model.ModelEntity;
+import org.ofbiz.entity.model.ModelField;
 import org.ofbiz.entity.testtools.EntityTestCase;
 import org.ofbiz.entity.transaction.GenericTransactionException;
 import org.ofbiz.entity.transaction.TransactionUtil;
@@ -61,6 +63,8 @@ public class EntityTestSuite extends Ent
      * with Derby.  Going up to 100,000 causes problems all around because Java List seems to be capped at about 65,000 values.
      *
      * NOTE: setting this lower so that the general tests don't take so long to run; to really push it can increase this number.
+     * NOTE: Let's try to distinguish between functional testing and stress testing. Any value greater than 1 will be sufficient
+     * for functional testing. Values like 10,000 or 100,000 are more appropriate for stress testing.
      */
     public static final long TEST_COUNT = 1000;
 
@@ -70,6 +74,20 @@ public class EntityTestSuite extends Ent
 
     final static private int _level1max = 3;   // number of TestingNode entities to create
 
+    public void testModels() throws Exception {
+        ModelEntity modelEntity = delegator.getModelEntity("TestingType");
+        assertNotNull("TestingType entity model not null", modelEntity);
+        ModelField modelField = modelEntity.getField("description");
+        assertNotNull("TestingType.description field model not null", modelField);
+        modelField = new ModelField("newDesc", modelField.getType(), "NEW_DESC", false);
+        modelEntity.addField(modelField);
+        modelField = modelEntity.getField("newDesc");
+        assertNotNull("TestingType.newDesc field model not null", modelField);
+        modelEntity.removeField("newDesc");
+        modelField = modelEntity.getField("newDesc");
+        assertNull("TestingType.newDesc field model is null", modelField);
+    }
+    
     /*
      * Tests storing values with the delegator's .create, .makeValue, and .storeAll methods
      */