You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by jo...@apache.org on 2008/04/30 04:02:29 UTC

svn commit: r652226 - in /ofbiz/trunk/framework: entity/dtd/ entity/entitydef/ entity/src/org/ofbiz/entity/ entity/src/org/ofbiz/entity/datasource/ entity/src/org/ofbiz/entity/jdbc/ entity/src/org/ofbiz/entity/model/ service/src/org/ofbiz/service/ weba...

Author: jonesde
Date: Tue Apr 29 19:02:29 2008
New Revision: 652226

URL: http://svn.apache.org/viewvc?rev=652226&view=rev
Log:
Added feature to entity engine to support generic audit log that can be used for any field; values are converted to a string and l using the enable-audit-log attribute on the entity->field element; the Testing entity uses it now for the testName; this also has functionality to associate user info like the userLoginId with the thread so that the EE knows about it automatically, and uses a stack for flexibility

Modified:
    ofbiz/trunk/framework/entity/dtd/entitymodel.xsd
    ofbiz/trunk/framework/entity/entitydef/entitygroup.xml
    ofbiz/trunk/framework/entity/entitydef/entitymodel.xml
    ofbiz/trunk/framework/entity/entitydef/entitymodel_test.xml
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/datasource/GenericDAO.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/DatabaseUtil.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelEntity.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelField.java
    ofbiz/trunk/framework/service/src/org/ofbiz/service/ServiceDispatcher.java
    ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/ControlServlet.java

Modified: ofbiz/trunk/framework/entity/dtd/entitymodel.xsd
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/dtd/entitymodel.xsd?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/dtd/entitymodel.xsd (original)
+++ ofbiz/trunk/framework/entity/dtd/entitymodel.xsd Tue Apr 29 19:02:29 2008
@@ -133,6 +133,15 @@
                 </xs:restriction>
             </xs:simpleType>
         </xs:attribute>
+        <xs:attribute name="enable-audit-log" default="false">
+            <xs:annotation><xs:documentation>If this is set to true then whenever the value for this field on a record changes the Entity Engine will record the change in the EntityAuditLog entity. Defaults to false.</xs:documentation></xs:annotation>
+            <xs:simpleType>
+                <xs:restriction base="xs:token">
+                    <xs:enumeration value="true"/>
+                    <xs:enumeration value="false"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
     </xs:attributeGroup>
     <xs:element name="validate">
         <xs:complexType>

Modified: ofbiz/trunk/framework/entity/entitydef/entitygroup.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/entitydef/entitygroup.xml?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/entitydef/entitygroup.xml (original)
+++ ofbiz/trunk/framework/entity/entitydef/entitygroup.xml Tue Apr 29 19:02:29 2008
@@ -21,16 +21,7 @@
 <entitygroup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/entitygroup.xsd">
 
-    <!-- ========================================================= -->
-    <!-- org.ofbiz.entity.sequence -->
-    <!-- ========================================================= -->
-
-    <entity-group group="org.ofbiz" entity="SequenceValueItem" />
-
-    <!-- ========================================================= -->
-    <!-- org.ofbiz.entity.crypto -->
-    <!-- ========================================================= -->
-
+    <entity-group group="org.ofbiz" entity="EntityAuditLog" />
     <entity-group group="org.ofbiz" entity="EntityKeyStore" />
+    <entity-group group="org.ofbiz" entity="SequenceValueItem" />
 </entitygroup>
-

Modified: ofbiz/trunk/framework/entity/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/entitydef/entitymodel.xml?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/framework/entity/entitydef/entitymodel.xml Tue Apr 29 19:02:29 2008
@@ -32,31 +32,30 @@
     <!-- ========================================================= -->
     <!-- ======================== Data Model ===================== -->
     <!-- The modules in this file are as follows:                  -->
-    <!--  - org.ofbiz.entity.sequence -->
+    <!--  - org.ofbiz.entity.audit -->
     <!--  - org.ofbiz.entity.crypto -->
+    <!--  - org.ofbiz.entity.sequence -->
     <!-- ========================================================= -->
 
-    <!-- ========================================================= -->
-    <!-- org.ofbiz.entity.sequence -->
-    <!-- ========================================================= -->
-
-    <entity entity-name="SequenceValueItem"
-            package-name="org.ofbiz.entity.sequence"
-            title="Sequence Entity">
+    <entity entity-name="EntityAuditLog" package-name="org.ofbiz.entity.audit" title="Entity Audit Log">
+        <field name="auditHistorySeqId" type="id-ne"><description>Sequenced primary key</description></field>
+        <field name="changedEntityName" type="long-varchar"></field>
+        <field name="changedFieldName" type="long-varchar"></field>
+        <field name="pkCombinedValueText" type="long-varchar"></field>
+        <field name="oldValueText" type="long-varchar"></field>
+        <field name="newValueText" type="long-varchar"></field>
+        <field name="changedDate" type="date-time"></field>
+        <field name="changedByInfo" type="long-varchar"><description>This should contain whatever information about the user or system that changed the value that is available. This could be a userLoginId, but could be something else too, so there is no foreign key.</description></field>
+        <prim-key field="auditHistorySeqId"/>
+    </entity>
+    <entity entity-name="EntityKeyStore" package-name="org.ofbiz.entity.crypto" title="Entity Key Store Entity">
+        <field name="keyName" type="id-vlong-ne"></field>
+        <field name="keyText" type="long-varchar"></field>
+        <prim-key field="keyName"/>
+    </entity>
+    <entity entity-name="SequenceValueItem" package-name="org.ofbiz.entity.sequence" title="Sequence Value Item Entity">
       <field name="seqName" type="id-long-ne"></field>
       <field name="seqId" type="numeric"></field>
       <prim-key field="seqName"/>
     </entity>
-
-    <!-- ========================================================= -->
-    <!-- org.ofbiz.entity.crypto -->
-    <!-- ========================================================= -->
-
-    <entity entity-name="EntityKeyStore"
-            package-name="org.ofbiz.entity.crypto"
-            title="Entity Key Entity">
-      <field name="keyName" type="id-vlong-ne"></field>
-      <field name="keyText" type="long-varchar"></field>
-      <prim-key field="keyName"/>
-    </entity>
 </entitymodel>

Modified: ofbiz/trunk/framework/entity/entitydef/entitymodel_test.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/entitydef/entitymodel_test.xml?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/entitydef/entitymodel_test.xml (original)
+++ ofbiz/trunk/framework/entity/entitydef/entitymodel_test.xml Tue Apr 29 19:02:29 2008
@@ -52,7 +52,7 @@
         title="Testing Entity">
         <field name="testingId" type="id-ne"/>
         <field name="testingTypeId" type="id-ne"/>
-        <field name="testingName" type="name"/>
+        <field name="testingName" type="name" enable-audit-log="true"/>
         <field name="description" type="description"/>
         <field name="comments" type="comment"/>
         <field name="testingSize" type="numeric"/>

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericDelegator.java Tue Apr 29 19:02:29 2008
@@ -41,6 +41,7 @@
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.GeneralRuntimeException;
+import org.ofbiz.base.util.UtilDateTime;
 import org.ofbiz.base.util.UtilFormatOut;
 import org.ofbiz.base.util.UtilMisc;
 import org.ofbiz.base.util.UtilValidate;
@@ -92,13 +93,16 @@
 
     protected Cache cache = null;
 
-    // keeps a list of field key sets used in the by and cache, a Set (of Sets of fieldNames) for each entityName
+    /** keeps a list of field key sets used in the by and cache, a Set (of Sets of fieldNames) for each entityName */
     protected Map andCacheFieldSets = FastMap.newInstance();
 
     protected DistributedCacheClear distributedCacheClear = null;
     protected EntityEcaHandler<?> entityEcaHandler = null;
     protected SequenceUtil sequencer = null;
     protected EntityCrypto crypto = null;
+    
+    /** A ThreadLocal variable to allow other methods to specify an identifier (usually the userLoginId, though technically the Entity Engine doesn't know anything about the UserLogin entity) */
+    protected static ThreadLocal<List<Object>> userIdentifierStack = new ThreadLocal<List<Object>>();
 
     public static GenericDelegator getGenericDelegator(String delegatorName) {
         if (delegatorName == null) {
@@ -130,6 +134,47 @@
         return delegator;
     }
 
+    protected static List<Object> getUserIdentifierStack() {
+        List<Object> curValList = userIdentifierStack.get();
+        if (curValList == null) {
+            curValList = FastList.newInstance();
+            userIdentifierStack.set(curValList);
+        }
+        return curValList;
+    }
+    
+    public static String getCurrentUserIdentifier() {
+        List<Object> curValList = getUserIdentifierStack();
+        Object curVal = curValList.get(0);
+        if (curVal == null) {
+            return null;
+        } else {
+            return curVal.toString();
+        }
+    }
+    
+    public static void pushUserIdentifier(String userIdentifier) {
+        if (userIdentifier == null) {
+            return;
+        }
+        List<Object> curValList = getUserIdentifierStack();
+        curValList.add(0, userIdentifier);
+    }
+    
+    public static String popUserIdentifier() {
+        List<Object> curValList = getUserIdentifierStack();
+        if (curValList.size() == 0) {
+            return null;
+        } else {
+            return (String) curValList.remove(0);
+        }
+    }
+    
+    public static void clearUserIdentifierStack() {
+        List<Object> curValList = getUserIdentifierStack();
+        curValList.clear();
+    }
+
     /** Only allow creation through the factory method */
     protected GenericDelegator() {}
 
@@ -250,7 +295,7 @@
     public String getDelegatorName() {
         return this.delegatorName;
     }
-
+    
     protected DelegatorInfo getDelegatorInfo() {
         if (delegatorInfo == null) {
             delegatorInfo = EntityConfigUtil.getDelegatorInfo(this.delegatorName);
@@ -618,6 +663,12 @@
 
             value.setDelegator(this);
             this.encryptFields(value);
+
+            // if audit log on for any fields, save new value with no old value because it's a create
+            if (value != null && value.getModelEntity().getHasFieldWithAuditLog()) {
+                createEntityAuditLogAll(value, false, false);
+            }
+            
             try {
                 value = helper.create(value);
             } catch (GenericEntityException e) {
@@ -651,6 +702,7 @@
             }
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RETURN, EntityEcaHandler.OP_CREATE, value, false);
+            
             return value;
         } catch (GenericEntityException e) {
             String errMsg = "Failure in create operation for entity [" + value.getEntityName() + "]: " + e.toString() + ". Rolling back transaction.";
@@ -668,7 +720,7 @@
             TransactionUtil.commit(beganTransaction);
         }
     }
-
+    
     /** Creates a Entity in the form of a GenericValue and write it to the datasource
      *@param value 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
@@ -693,6 +745,12 @@
 
             value.setDelegator(this);
             this.encryptFields(value);
+
+            // if audit log on for any fields, save new value with no old value because it's a create
+            if (value != null && value.getModelEntity().getHasFieldWithAuditLog()) {
+                createEntityAuditLogAll(value, false, false);
+            }
+            
             value = helper.create(value);
 
             if (value != null) {
@@ -708,6 +766,7 @@
             }
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RETURN, EntityEcaHandler.OP_CREATE, value, false);
+            
             return value;
         } catch (GenericEntityException e) {
             String errMsg = "Failure in create operation for entity [" + value.getEntityName() + "]: " + e.toString() + ". Rolling back transaction.";
@@ -836,10 +895,17 @@
             }
 
             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
+            if (primaryKey != null && primaryKey.getModelEntity().getHasFieldWithAuditLog()) {
+                createEntityAuditLogAll(this.findByPrimaryKey(primaryKey), true, true);
+            }
+            
             int num = helper.removeByPrimaryKey(primaryKey);
             this.saveEntitySyncRemoveInfo(primaryKey);
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RETURN, EntityEcaHandler.OP_REMOVE, primaryKey, false);
+
             return num;
         } catch (GenericEntityException e) {
             String errMsg = "Failure in removeByPrimaryKey operation for entity [" + primaryKey.getEntityName() + "]: " + e.toString() + ". Rolling back transaction.";
@@ -890,10 +956,17 @@
             }
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_REMOVE, value, false);
+            
+            // if audit log on for any fields, save old value before actual remove
+            if (value != null && value.getModelEntity().getHasFieldWithAuditLog()) {
+                createEntityAuditLogAll(value, true, true);
+            }
+            
             int num = helper.removeByPrimaryKey(value.getPrimaryKey());
             this.saveEntitySyncRemoveInfo(value.getPrimaryKey());
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RETURN, EntityEcaHandler.OP_REMOVE, value, false);
+            
             return num;
         } catch (GenericEntityException e) {
             String errMsg = "Failure in removeValue operation for entity [" + value.getEntityName() + "]: " + e.toString() + ". Rolling back transaction.";
@@ -1147,6 +1220,12 @@
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_STORE, value, false);
             this.encryptFields(value);
+            
+            // if audit log on for any fields, save old value before the update so we still have both
+            if (value != null && value.getModelEntity().getHasFieldWithAuditLog()) {
+                createEntityAuditLogAll(value, true, false);
+            }
+            
             int retVal = helper.store(value);
 
             // refresh the valueObject to get the new version
@@ -1155,6 +1234,7 @@
             }
 
             ecaRunner.evalRules(EntityEcaHandler.EV_RETURN, EntityEcaHandler.OP_STORE, value, false);
+            
             return retVal;
         } catch (GenericEntityException e) {
             String errMsg = "Failure in store operation for entity [" + value.getEntityName() + "]: " + e.toString() + ". Rolling back transaction.";
@@ -3001,6 +3081,73 @@
         return cache;
     }
 
+    protected void createEntityAuditLogAll(GenericValue value, boolean isUpdate, boolean isRemove) throws GenericEntityException {
+        for (ModelField mf: value.getModelEntity().getFieldsUnmodifiable()) {
+            if (mf.getEnableAuditLog()) {
+                createEntityAuditLogSingle(value, mf, isUpdate, isRemove);
+            }
+        }
+    }
+    
+    protected void createEntityAuditLogSingle(GenericValue value, ModelField mf, boolean isUpdate, boolean isRemove) throws GenericEntityException {
+        if (value == null || mf == null || !mf.getEnableAuditLog()) {
+            return;
+        }
+        
+        GenericValue entityAuditLog = this.makeValue("EntityAuditLog");
+        entityAuditLog.set("auditHistorySeqId", this.getNextSeqId("EntityAuditLog"));
+        entityAuditLog.set("changedEntityName", value.getEntityName());
+        entityAuditLog.set("changedFieldName", mf.getName());
+        
+        String pkCombinedValueText = value.getPkShortValueString();
+        if (pkCombinedValueText.length() > 250) {
+            // uh-oh, the string is too long!
+            pkCombinedValueText = pkCombinedValueText.substring(0, 250);
+        }
+        entityAuditLog.set("pkCombinedValueText", pkCombinedValueText);
+        
+        GenericValue oldGv = null;
+        if (isUpdate) {
+            // it's an update, get it from the database
+            oldGv = this.findByPrimaryKey(value.getPrimaryKey());
+        } else if (isRemove) {
+            oldGv = value;
+        }
+        if (oldGv == null) {
+            if (isUpdate || isRemove) {
+                entityAuditLog.set("oldValueText", "[ERROR] Old value not found even though it was an update or remove");
+            }
+        } else {
+            // lookup old value
+            String oldValueText = null; 
+            Object oldValue = oldGv.get(mf.getName());
+            if (oldValue != null) {
+                oldValueText = oldValue.toString();
+                if (oldValueText.length() > 250) {
+                    oldValueText = oldValueText.substring(0, 250);
+                }
+            }
+            entityAuditLog.set("oldValueText", oldValueText);
+        }
+        
+        if (!isRemove) {
+            String newValueText = null;
+            Object newValue = value.get(mf.getName());
+            if (newValue != null) {
+                newValueText = newValue.toString();
+                if (newValueText.length() > 250) {
+                    newValueText = newValueText.substring(0, 250);
+                }
+            }
+            entityAuditLog.set("newValueText", newValueText);
+        }
+
+        entityAuditLog.set("changedDate", UtilDateTime.nowTimestamp());
+        entityAuditLog.set("changedByInfo", getCurrentUserIdentifier());
+        
+        this.create(entityAuditLog);
+    }
+
     public GenericDelegator cloneDelegator(String delegatorName) {
         // creates an exact clone of the delegator; except for the sequencer
         // note that this will not be cached and should be used only when

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/GenericEntity.java Tue Apr 29 19:02:29 2008
@@ -316,9 +316,7 @@
     }
     public boolean isPrimaryKey(boolean requireValue) {
         TreeSet<String> fieldKeys = new TreeSet<String>(this.fields.keySet());
-        Iterator<ModelField> pkIter = getModelEntity().getPksIterator();
-        while (pkIter.hasNext()) {
-            ModelField curPk = pkIter.next();
+        for (ModelField curPk: this.getModelEntity().getPkFieldsUnmodifiable()) {
             String fieldName = curPk.getName();
             if (requireValue) {
                 if (this.fields.get(fieldName) == null) return false;
@@ -337,9 +335,7 @@
     }
     public boolean containsPrimaryKey(boolean requireValue) {
         //TreeSet fieldKeys = new TreeSet(fields.keySet());
-        Iterator pkIter = getModelEntity().getPksIterator();
-        while (pkIter.hasNext()) {
-            ModelField curPk = (ModelField) pkIter.next();
+        for (ModelField curPk: this.getModelEntity().getPkFieldsUnmodifiable()) {
             String fieldName = curPk.getName();
             if (requireValue) {
                 if (this.fields.get(fieldName) == null) return false;
@@ -349,6 +345,17 @@
         }
         return true;
     }
+    
+    public String getPkShortValueString() {
+        StringBuffer sb = new StringBuffer();
+        for (ModelField curPk: this.getModelEntity().getPkFieldsUnmodifiable()) {
+            if (sb.length() > 0) {
+                sb.append("::");
+            }
+            sb.append(this.get(curPk.getName()));
+        }
+        return sb.toString();
+    }
 
     /** Sets the named field to the passed value, even if the value is null
      * @param name The field name to set

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/datasource/GenericDAO.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/datasource/GenericDAO.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/datasource/GenericDAO.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/datasource/GenericDAO.java Tue Apr 29 19:02:29 2008
@@ -108,7 +108,7 @@
         SQLProcessor sqlP = new SQLProcessor(helperName);
 
         try {
-            return singleInsert(entity, modelEntity, modelEntity.getFieldsCopy(), sqlP);
+            return singleInsert(entity, modelEntity, modelEntity.getFieldsUnmodifiable(), sqlP);
         } catch (GenericEntityException e) {
             sqlP.rollback();
             // no need to create nested, just throw original which will have all info: throw new GenericEntityException("Exception while inserting the following entity: " + entity.toString(), e);
@@ -259,7 +259,7 @@
         }
 
         String sql = "UPDATE " + modelEntity.getTableName(datasourceInfo) + " SET " + modelEntity.colNameString(fieldsToSave, "=?, ", "=?", false) + " WHERE " +
-            SqlJdbcUtil.makeWhereStringFromFields(modelEntity.getPksCopy(), entity, "AND");
+            SqlJdbcUtil.makeWhereStringFromFields(modelEntity.getPkFieldsUnmodifiable(), entity, "AND");
 
         int retVal = 0;
 
@@ -461,7 +461,7 @@
              * If not all member entities can be updated, then none should be updated
              */
             if (meResult.size() == 0) {
-                retVal += singleInsert(meGenericValue, memberModelEntity, memberModelEntity.getFieldsCopy(), sqlP);
+                retVal += singleInsert(meGenericValue, memberModelEntity, memberModelEntity.getFieldsUnmodifiable(), sqlP);
             } else {
                 if (meFieldsToSave.size() > 0) {
                     retVal += singleUpdate(meGenericValue, memberModelEntity, meFieldsToSave, sqlP);
@@ -508,7 +508,7 @@
         }
 
         sqlBuffer.append(SqlJdbcUtil.makeFromClause(modelEntity, datasourceInfo));
-        sqlBuffer.append(SqlJdbcUtil.makeWhereClause(modelEntity, modelEntity.getPksCopy(), entity, "AND", datasourceInfo.joinStyle));
+        sqlBuffer.append(SqlJdbcUtil.makeWhereClause(modelEntity, modelEntity.getPkFieldsUnmodifiable(), entity, "AND", datasourceInfo.joinStyle));
 
         try {
             sqlP.prepareStatement(sqlBuffer.toString(), true, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
@@ -577,7 +577,7 @@
             sqlBuffer.append("*");
         }
         sqlBuffer.append(SqlJdbcUtil.makeFromClause(modelEntity, datasourceInfo));
-        sqlBuffer.append(SqlJdbcUtil.makeWhereClause(modelEntity, modelEntity.getPksCopy(), entity, "AND", datasourceInfo.joinStyle));
+        sqlBuffer.append(SqlJdbcUtil.makeWhereClause(modelEntity, modelEntity.getPkFieldsUnmodifiable(), entity, "AND", datasourceInfo.joinStyle));
 
         SQLProcessor sqlP = new SQLProcessor(helperName);
 
@@ -650,7 +650,7 @@
                 throw new GenericModelException("In selectListIteratorByCondition invalid field names specified: " + tempKeys.toString());
             }
         } else {
-            selectFields = modelEntity.getFieldsCopy();
+            selectFields = modelEntity.getFieldsUnmodifiable();
         }
 
         StringBuilder sqlBuffer = new StringBuilder("SELECT ");
@@ -1007,7 +1007,7 @@
             throw new org.ofbiz.entity.GenericNotImplementedException("Operation delete not supported yet for view entities");
         }
 
-        String sql = "DELETE FROM " + modelEntity.getTableName(datasourceInfo) + " WHERE " + SqlJdbcUtil.makeWhereStringFromFields(modelEntity.getPksCopy(), entity, "AND");
+        String sql = "DELETE FROM " + modelEntity.getTableName(datasourceInfo) + " WHERE " + SqlJdbcUtil.makeWhereStringFromFields(modelEntity.getPkFieldsUnmodifiable(), entity, "AND");
 
         int retVal;
 

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/DatabaseUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/DatabaseUtil.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/DatabaseUtil.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/jdbc/DatabaseUtil.java Tue Apr 29 19:02:29 2008
@@ -1320,9 +1320,7 @@
                     }
                     if (pkCount == 0) {
                         Debug.logInfo("Searching in " + tableNames.size() + " tables for primary key fields ...", module);
-                        Iterator it = tableNames.iterator();
-                        while (it.hasNext()) {
-                            String curTable = (String) it.next();
+                        for (String curTable: tableNames) {
                             curTable = curTable.substring(curTable.indexOf('.') + 1); //cut off schema name
                             ResultSet rsPks = dbData.getPrimaryKeys(null, lookupSchemaName, curTable);
                             pkCount += checkPrimaryKeyInfo(rsPks, lookupSchemaName, needsUpperCase, colInfo, messages);
@@ -1765,7 +1763,7 @@
             sqlBuf.append(pkName);
         }
         sqlBuf.append(" PRIMARY KEY (");
-        sqlBuf.append(entity.colNameString(entity.getPksCopy()));
+        sqlBuf.append(entity.colNameString(entity.getPkFieldsUnmodifiable()));
         sqlBuf.append(")");
 
         if (addFks) {
@@ -2597,7 +2595,7 @@
                 sqlBuf.append(pkName);
             }
             sqlBuf.append(" PRIMARY KEY (");
-            sqlBuf.append(entity.colNameString(entity.getPksCopy()));
+            sqlBuf.append(entity.colNameString(entity.getPkFieldsUnmodifiable()));
             sqlBuf.append(")");
 
             if (Debug.verboseOn()) Debug.logVerbose("[createPrimaryKey] sql=" + sqlBuf.toString(), module);

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=652226&r1=652225&r2=652226&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 Tue Apr 29 19:02:29 2008
@@ -22,6 +22,7 @@
 import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -116,6 +117,8 @@
 
     protected boolean autoClearCache = true;
 
+    protected Boolean hasFieldWithAuditLog = null;
+
     /** The location of this entity's definition */
     protected String location = "";
 
@@ -382,6 +385,20 @@
     public void setAutoClearCache(boolean autoClearCache) {
         this.autoClearCache = autoClearCache;
     }
+    
+    public boolean getHasFieldWithAuditLog() {
+        if (this.hasFieldWithAuditLog == null) {
+            this.hasFieldWithAuditLog = false;
+            for (ModelField mf: this.fields) {
+                if (mf.getEnableAuditLog()) {
+                    this.hasFieldWithAuditLog = true;
+                }
+            }
+            return this.hasFieldWithAuditLog;
+        } else {
+            return this.hasFieldWithAuditLog;
+        }
+    }
 
     /* Get the location of this entity's definition */
     public String getLocation() {
@@ -466,11 +483,18 @@
         return this.pks.iterator();
     }
 
+    /**
+     * @deprecated Use getPkFieldsUnmodifiable instead.
+     */
     public List<ModelField> getPksCopy() {
         List<ModelField> newList = FastList.newInstance();
         newList.addAll(this.pks);
         return newList;
     }
+    
+    public List<ModelField> getPkFieldsUnmodifiable() {
+        return Collections.unmodifiableList(this.pks);
+    }
 
     public String getFirstPkFieldName() {
         List<String> pkFieldNames = this.getPkFieldNames();
@@ -517,12 +541,19 @@
         return this.fields.iterator();
     }
 
+    /**
+     * @deprecated Use getFieldsUnmodifiable instead.
+     */
     public List<ModelField> getFieldsCopy() {
         List<ModelField> newList = FastList.newInstance();
         newList.addAll(this.fields);
         return newList;
     }
 
+    public List<ModelField> getFieldsUnmodifiable() {
+        return Collections.unmodifiableList(this.fields);
+    }
+
     /** The col-name of the Field, the alias of the field if this is on a view-entity */
     public String getColNameOrAlias(String fieldName) {
         ModelField modelField = this.getField(fieldName);

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelField.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelField.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelField.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/model/ModelField.java Tue Apr 29 19:02:29 2008
@@ -43,6 +43,7 @@
     protected boolean isPk = false;
     protected boolean encrypt = false;
     protected boolean isAutoCreatedInternal = false;
+    protected boolean enableAuditLog = false;
     
     /** validators to be called when an update is done */
     protected List<String> validators = new ArrayList<String>();
@@ -52,15 +53,16 @@
 
     /** Fields Constructor */
     public ModelField(String name, String type, String colName, boolean isPk) {
-        this(name, type, colName, isPk, false);
+        this(name, type, colName, isPk, false, false);
     }
 
-    public ModelField(String name, String type, String colName, boolean isPk, boolean encrypt) {
+    public ModelField(String name, String type, String colName, boolean isPk, boolean encrypt, boolean enableAuditLog) {
         this.name = name;
         this.type = type;
         this.setColName(colName);
         this.isPk = isPk;
         this.encrypt = encrypt;
+        this.enableAuditLog = enableAuditLog;
     }
 
     /** XML Constructor */
@@ -71,6 +73,7 @@
         this.isPk = false; // is set elsewhere
         this.encrypt = UtilXml.checkBoolean(fieldElement.getAttribute("encrypt"), false);
         this.description = UtilXml.childElementValue(fieldElement, "description");
+        this.enableAuditLog = UtilXml.checkBoolean(fieldElement.getAttribute("enable-audit-log"), false);
 
         NodeList validateList = fieldElement.getElementsByTagName("validate");
 
@@ -138,6 +141,10 @@
     public void setEncrypt(boolean encrypt) {
         this.encrypt = encrypt;
     }
+    
+    public boolean getEnableAuditLog() {
+        return this.enableAuditLog;
+    }
 
     public boolean getIsAutoCreatedInternal() {
         return this.isAutoCreatedInternal;

Modified: ofbiz/trunk/framework/service/src/org/ofbiz/service/ServiceDispatcher.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/service/src/org/ofbiz/service/ServiceDispatcher.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/service/src/org/ofbiz/service/ServiceDispatcher.java (original)
+++ ofbiz/trunk/framework/service/src/org/ofbiz/service/ServiceDispatcher.java Tue Apr 29 19:02:29 2008
@@ -313,7 +313,6 @@
             }
 
             try {
-
                 int lockRetriesRemaining = LOCK_RETRIES;
                 boolean needsLockRetry = false;
                 
@@ -341,12 +340,17 @@
                     //Debug.logInfo("After [" + modelService.name + "] pre-auth ECA, before auth; isFailure=" + isFailure + ", isError=" + isError, module);
 
                     context = checkAuth(localName, context, modelService);
-                    Object userLogin = context.get("userLogin");
+                    GenericValue userLogin = (GenericValue) context.get("userLogin");
 
                     if (modelService.auth && userLogin == null) {
                         throw new ServiceAuthException("User authorization is required for this service: " + modelService.name + modelService.debugInfo());
                     }
 
+                    // now that we have authed, if there is a userLogin, set the EE userIdentifier
+                    if (userLogin != null && userLogin.getString("userLoginId") != null) {
+                        GenericDelegator.pushUserIdentifier(userLogin.getString("userLoginId"));
+                    }
+                    
                     // pre-validate ECA
                     if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "in-validate", ctx, context, result, isError, isFailure);
 
@@ -539,6 +543,9 @@
 
                 // call notifications -- event is determined from the result (success, error, fail)
                 modelService.evalNotifications(this.getLocalContext(localName), context, result);
+                
+                // clear out the EE userIdentifier
+                GenericDelegator.popUserIdentifier();
             }
         } catch (GenericTransactionException te) {
             Debug.logError(te, "Problems with the transaction", module);

Modified: ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/ControlServlet.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/ControlServlet.java?rev=652226&r1=652225&r2=652226&view=diff
==============================================================================
--- ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/ControlServlet.java (original)
+++ ofbiz/trunk/framework/webapp/src/org/ofbiz/webapp/control/ControlServlet.java Tue Apr 29 19:02:29 2008
@@ -108,6 +108,11 @@
         GenericValue userLogin = (GenericValue) session.getAttribute("userLogin");
         //Debug.log("Cert Chain: " + request.getAttribute("javax.servlet.request.X509Certificate"), module);
 
+        // set the Entity Engine user info if we have a userLogin
+        if (userLogin != null) {
+            GenericDelegator.pushUserIdentifier(userLogin.getString("userLoginId"));
+        }
+        
         // workaraound if we are in the root webapp
         String webappName = UtilHttp.getApplicationName(request);
 
@@ -176,7 +181,7 @@
         // setup some things that should always be there
         UtilHttp.setInitialRequestInfo(request);
         VisitHandler.getVisitor(request, response);
-
+        
         // display details on the servlet objects
         if (Debug.verboseOn()) {
             logRequestInfo(request);
@@ -255,7 +260,7 @@
             }
         }
 
-        // sanity check; make sure we don't have any transactions in place
+        // sanity check: make sure we don't have any transactions in place
         try {
             // roll back current TX first
             if (TransactionUtil.isTransactionInPlace()) {
@@ -271,6 +276,9 @@
         } catch (GenericTransactionException e) {
             Debug.logWarning(e, module);
         }
+        
+        // sanity check 2: make sure there are no user infos in the delegator, ie clear the thread
+        GenericDelegator.clearUserIdentifierStack();
 
         // run these two again before the ServerHitBin.countRequest call because on a logout this will end up creating a new visit
         if (response.isCommitted() && request.getSession(false) == null) {