[cvs] expresso commit by lhamel: add isMutable() attribute and tests for

JCorporate Ltd jcorp at jcorporate.com
Mon Feb 21 01:14:44 UTC 2005


Log Message:
-----------
add isMutable() attribute and tests for same.  moved some methods to group statics and instance vars together.

Modified Files:
--------------
    expresso/expresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj:
        DBObject.java

Revision Data
-------------
Index: DBObject.java
===================================================================
RCS file: /home/javacorp/.cvs/expresso/expresso/expresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj/DBObject.java,v
retrieving revision 1.241
retrieving revision 1.242
diff -Lexpresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj/DBObject.java -Lexpresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj/DBObject.java -u -r1.241 -r1.242
--- expresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj/DBObject.java
+++ expresso-web/WEB-INF/src/com/jcorporate/expresso/core/dbobj/DBObject.java
@@ -115,6 +115,7 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -132,7 +133,7 @@
  * <p>When making your own application, you derive your classes from DBObject or
  * SecuredDBObject.</p>
  *
- * @author Michael Nash
+ * @author Michael Nash, adaptations Larry Hamel
  * @see com.jcorporate.expresso.core.dbobj.SecuredDBObject
  * @see com.jcorporate.expresso.core.db.DBException
  * @see com.jcorporate.expresso.core.db.DBConnection
@@ -168,6 +169,12 @@
     public static final String ATTRIBUTE_PAGE_LIMIT = "pageLimit";
 
     /**
+     * attribute name for whether this object is mutable;
+     * defaults to true;
+     */
+    public static final String IS_MUTABLE = "IS_MUTABLE";
+
+    /**
      * Event 'Add' code
      */
     public static final String EVENT_ADD = "A";
@@ -181,7 +188,14 @@
      * Event 'Update' Code
      */
     public static final String EVENT_UPDATE = "U";
+    public static final String WHERE_KEYWORD = " WHERE ";
 
+    /**
+     * setup flag name for checking relational integrity;
+     * set this to false if your DB already checks this, and you want to avoid the performance
+     * hit associated with checking this in the middleware.
+     */
+    public static final String IS_CHECK_RELATIONAL_INTEGRITY = "isCheckRelationalIntegrity";
 
     /**
      * A static zero BIG DECIMAL object
@@ -192,6 +206,25 @@
      */
     transient protected static final BigDecimal BIG_DECIMAL_ZERO = new BigDecimal(0.0);
 
+    /**
+     * Integer Regular Expression for easy reference
+     */
+    public static final String INT_MASK = "^[+-]?[0-9]+";
+
+    /**
+     * Floating point regular expression syntax for easy reference.
+     */
+    public static final String FLOAT_MASK = "^([+-]?)(?=\\d|\\.\\d)\\d*(\\.\\d*)?([Ee]([+-]?\\d+))?$";
+
+    /**
+     * Email Regular Expression Constant.
+     */
+    public static final String EMAIL_MASK =
+            "^[A-Za-z0-9\\-\\.\\_]+"
+            + "@"
+            + "[A-Za-z0-9\\-\\.\\_]+"
+            + "\\."
+            + "[a-zA-Z]{2,6}$";  // 6 chars in "museum" TLD
 
     /* the logs
     * @todo move this static variable outside DBObject so we can wrap it for
@@ -199,7 +232,6 @@
     */
     transient private static Logger log = Logger.getLogger(DBObject.class);
 
-
     /**
      * 30 minute DBObjLimit cache tty
      */
@@ -207,8 +239,9 @@
 
     /**
      * Statistics for Cache entries.
+     * @todo move this to cache system
      */
-    transient private static ConcurrentReaderHashMap sCacheStats = new ConcurrentReaderHashMap();
+    transient protected static ConcurrentReaderHashMap sCacheStats = new ConcurrentReaderHashMap();
 
 
     /**
@@ -226,17 +259,6 @@
         }
     };
     private static Logger slog = null;
-    public static final String WHERE_KEYWORD = " WHERE ";
-
-    /**
-     * Retrieve a thread local instance of the Perl5 pattern matcher.  Allows
-     * for optimization of # of instances of pattern matcher vs synchronization.
-     *
-     * @return PatternMatcher
-     */
-    protected PatternMatcher getPatternMatcher() {
-        return (PatternMatcher) patternMatcher.get();
-    }
 
 
     /**
@@ -266,14 +288,12 @@
      */
     private Map fieldData = null;
 
-
     /**
      * Contains a map of DBObject.FieldError classes describing the errors
      * set by checkField().
      */
     private HashMap fieldErrors = null;
 
-
     /* This is the locale of the DBObject. Originally this attribute
      * was part of the subclass <code>SecuredDBObject</code>. However
      * there is no reason that explains why all DBObjects should not
@@ -285,7 +305,6 @@
      */
     private Locale myLocale = Locale.getDefault();
 
-
     /**
      * Keys that have been found in the last retrieve.
      */
@@ -296,7 +315,9 @@
      */
     private HashMap attributes = null;
 
-    /* The cache size of this particular DB object */
+    /**
+     *  The cache size of this particular DB object
+     */
     private int myCacheSize = -2;
 
 
@@ -308,27 +329,6 @@
 
 
     /**
-     * Integer Regular Expression for easy reference
-     */
-    public static final String INT_MASK = "^[+-]?[0-9]+";
-
-    /**
-     * Floating point regular expression syntax for easy reference.
-     */
-    public static final String FLOAT_MASK = "^([+-]?)(?=\\d|\\.\\d)\\d*(\\.\\d*)?([Ee]([+-]?\\d+))?$";
-
-    /**
-     * Email Regular Expression Constant.
-     */
-    public static final String EMAIL_MASK =
-            "^[A-Za-z0-9\\-\\.\\_]+"
-            + "@"
-            + "[A-Za-z0-9\\-\\.\\_]+"
-            + "\\."
-            + "[a-zA-Z]{2,6}$";  // 6 chars in "museum" TLD
-    public static final String IS_CHECK_RELATIONAL_INTEGRITY = "isCheckRelationalIntegrity";
-
-    /**
      * Default Constructor. This allows a DB object to be dynamically
      * instantiated (e.g. loaded
      * with Class.forName()) and does all of the required initializations.
@@ -399,6 +399,19 @@
     }
 
     /**
+     * Initialize this DBObject and set the db/context to the specified key
+     *
+     * @param newdbKey The database Context name
+     * @throws DBException upon error.
+     */
+    public DBObject(String newdbKey)
+            throws DBException {
+        this();
+        setDataContext(newdbKey);
+    } /* DBObject(String) */
+
+
+    /**
      * Get the current locale for this dbobject
      *
      * @return The currently set locale or null if there is no locale set.
@@ -419,18 +432,6 @@
     }
 
     /**
-     * Initialize this DBObject and set the db/context to the specified key
-     *
-     * @param newdbKey The database Context name
-     * @throws DBException upon error.
-     */
-    public DBObject(String newdbKey)
-            throws DBException {
-        this();
-        setDataContext(newdbKey);
-    } /* DBObject(String) */
-
-    /**
      * Add a new record to the target table.
      * Assumes that the fields of this object are populated with data for the new
      * record. All key fields at least must be supplied with values, and all fields
@@ -442,7 +443,7 @@
      */
     public void add()
             throws DBException {
-
+        checkMutable();
         getExecutor().add(this);
 
         /* Now log the change if we are logging */
@@ -464,8 +465,6 @@
             myUpdates = null;
         } /* if */
 
-        // after add(), we know that 'current' values are now the baseline for comparison
-        cacheIsChangedComparison();
         setStatus(BaseDataObject.STATUS_CURRENT);
         notifyListeners(EVENT_ADD);
     } /* add() */
@@ -484,6 +483,8 @@
      */
     public synchronized int loadFromConnection(DBConnection connection)
             throws DBException {
+        checkMutable();
+
         String oneFieldName = null;
         Object tmpData = null;
         int fieldCount = 0;
@@ -548,7 +549,7 @@
 
 
         setDataContext(getDataContext());
-        cacheIsChangedComparison();
+        updateIsChanged();
         setStatus(BaseDataObject.STATUS_CURRENT);
 
         return fieldCount;
@@ -560,8 +561,13 @@
      * considered 'original value' for purposes of determining 'isChanged'
      *
      * @throws DBException upon error.
+     * @deprecated 2/05 v5.6 made protected instead of public; use updateIsChangedFlag() instead
      */
     public void cacheIsChangedComparison() throws DBException {
+        updateIsChanged();
+    }
+
+    protected void updateIsChanged() throws DBException {
         for (Iterator i = getMetaData().getFieldListArray().iterator(); i.hasNext();) {
             String oneFieldName = (String) i.next();
             DataField field = getDataField(oneFieldName);
@@ -1246,7 +1252,6 @@
             myUpdates = null;
         } /* if */
 
-        cacheIsChangedComparison();
         setStatus(BaseDataObject.STATUS_CURRENT);
         notifyListeners(EVENT_ADD);
         NextNumber.getInstance().reset(getDataContext(), this);
@@ -2271,14 +2276,14 @@
      */
     public boolean find()
             throws DBException {
-
+        checkMutable();
 
         //
         //If we have all the keys, then use retrieve which is much faster
         //than find.
         //
         boolean haveAllKeys = false;
-        com.jcorporate.expresso.core.dataobjects.jdbc.JDBCObjectMetaData metadata = getJDBCMetaData();
+        JDBCObjectMetaData metadata = getJDBCMetaData();
 
         ArrayList keyfields = metadata.getKeyFieldListArray();
         int keysize = keyfields.size();
@@ -2500,7 +2505,7 @@
                 myUpdates = null;
             }
 
-            cacheIsChangedComparison();
+            updateIsChanged();
             setStatus(BaseDataObject.STATUS_CURRENT);
 
             if (isCached() && !anyFieldsToRetrieve) {
@@ -2594,8 +2599,8 @@
 
 
     /**
-     * Return an "attribute". Attributes are temporary (e.g. not stored in the DBMS)
-     * values associated with this particular DB object instance.
+     * Return an attribute. Attributes are temporary (e.g. not stored in the DBMS)
+     * values associated with this particular object instance.
      *
      * @param attribName The attribute name to check
      * @return the object associated with this attribute
@@ -2615,6 +2620,7 @@
      * @param fieldName The field name to get the attirbutes for
      * @return the Iterator for all attributes associated with this field
      * @throws DBException upon error.
+     * @deprecated 2/05 v5.6; name change: use getFieldAttributesIterator instead
      */
     public Iterator getAttributesIterator(String fieldName)
             throws DBException {
@@ -2622,6 +2628,18 @@
     }
 
     /**
+     * Get an iterator for all of the STATIC field attributes specified for a field
+     *
+     * @param fieldName The field name to get the attirbutes for
+     * @return the Iterator for all attributes associated with this field
+     * @throws DBException upon error.
+     */
+    public Iterator getFieldAttributesIterator(String fieldName)
+            throws DBException {
+        return getDef().getAttributesIterator(fieldName);
+    }
+
+    /**
      * Gets the set size of the cache for this DBOBject
      *
      * @return The number of cache objects available for this object.
@@ -3296,6 +3314,8 @@
      */
     public void saveBinaryField(String fieldName,
                                 byte[] incomingData) throws DBException {
+        checkMutable();
+
         DBConnectionPool connectionPool = null;
         DBConnection myConnection = getLocalConnection();
 
@@ -3678,6 +3698,10 @@
             throws DBException {
         try {
 
+            if ( getLogger().isDebugEnabled() ) {
+                getLogger().debug("For better efficiency, override method: getThisDBObj() in object: "
+                        + getClass().getName());
+            }
             DBObject returnObj = (DBObject) getClass().newInstance();
             return returnObj;
 
@@ -3809,7 +3833,7 @@
         }
 //
 //        fieldData = new HashMap(dto.getTableFields());
-        cacheIsChangedComparison();
+        updateIsChanged();
         setStatus(BaseDataObject.STATUS_CURRENT);
     }
 
@@ -4083,7 +4107,6 @@
             }
 
             String cacheName = myClassName + ".valueField:" + valueField + "|descrip:" + descripField + "|where:" + whereClause + "|sort:" + sortKeyString;
-            CacheManager.getInstance();
             CacheSystem cs = CacheManager.getCacheSystem(getDataContext());
             if (cs != null) {
                 if (!cs.existsCache(cacheName)) {
@@ -4295,7 +4318,6 @@
             Locale oneLocale = Locale.getDefault();
 
             String cacheName = myClassName + "." + oneLocale.toString() + ".valueField:" + valueField + "|descrip:" + descripField + "|where:" + whereClause + "|sort:" + sortKeyString;
-            CacheManager.getInstance();
             CacheSystem cs = CacheManager.getCacheSystem(getDataContext());
 
             if (cs != null && !cs.existsCache(cacheName)) {
@@ -4628,7 +4650,6 @@
     * author Yves Henri Amaizo <amy_amaizo at compuserve.com>
     *
     * @return true if there are specific fields to input
-    * @see #isFieldsToInput(String)
     */
     public synchronized boolean isFieldsToInput()
                 throws DBException {
@@ -4826,12 +4847,8 @@
         /* Tell the cache manager to clear our db object cache and */
         /*  valid value cache */
         try {
-            CacheManager.getInstance();
             CacheSystem cs = CacheManager.getCacheSystem(getDataContext());
             //Caching is not enabled if cs == null
-            if (cs == null) {
-                return;
-            }
 
             if (EVENT_ADD.equals(eventCode)) {
                 //Add will automatically put it in the cache, which, in turn
@@ -4839,14 +4856,26 @@
                 //While it may seem odd that we're removing it when an item
                 //is added, right now, auto-inc fields won't necessarily
                 //get transferred properly into the cache.
-                cs.removeItem(myClassName, this);
+                if (cs != null) {
+                    cs.removeItem(myClassName, this);
+                }
+                // after update, we know that 'current' values are now the baseline for comparison
+                updateIsChanged();
+
             } else if (EVENT_UPDATE.equals(eventCode)) {
                 //Add will automatically put it in the cache, which, in turn
                 //will clear out related values
-                cacheUtils.addModifiedToCache(this);
+                if (cs != null) {
+                    cacheUtils.addModifiedToCache(this);
+                }
+                // after update, we know that 'current' values are now the baseline for comparison
+                updateIsChanged();
+
             } else if (EVENT_DELETE.equals(eventCode)) {
                 //Removal will clear out related values
-                cs.removeItem(myClassName, this);
+                if (cs != null) {
+                    cs.removeItem(myClassName, this);
+                }
             } else {
                 throw new DBException("Unknown Notification Code: " + eventCode);
             }
@@ -5014,7 +5043,6 @@
     public synchronized void removeFromCache(DBObject theDBObj)
             throws DBException {
         try {
-            CacheManager.getInstance();
             CacheManager.removeItem(theDBObj.getDataContext(),
                     theDBObj.myClassName, theDBObj);
         } catch (CacheException ce) {
@@ -5034,7 +5062,8 @@
 
 
     /**
-     * Get a particular record from the database into this object's fields
+     * Get a particular record from the database into this object's fields.
+     * Does NOT attempt fetch from cache first.
      * Key fields for this object must be set; throws otherwise
      *
      * @throws DBRecordNotFoundException If the record could not be retrieved.
@@ -5043,7 +5072,7 @@
      */
     public void retrieve()
             throws DBException {
-
+        checkMutable();
 
         if (!haveAllKeys()) {
             throw new DBRecordNotFoundException("(" +
@@ -5063,7 +5092,7 @@
         }
 
         // after retrieve, we know that 'current' values are now the baseline for comparison
-        cacheIsChangedComparison();
+        updateIsChanged();
         setStatus(BaseDataObject.STATUS_CURRENT);
 
         if (isCached() && !anyFieldsToRetrieve) {
@@ -5080,12 +5109,11 @@
             }
         }
 
-
     } /* retrieve() */
 
 
     /**
-     * Retrieve this object from cache, if possible.
+     * Retrieve this object from cache, if possible.  Makes a new copy of cached object.
      *
      * @return true if the cache supplied this item, false
      *         otherwise
@@ -5094,7 +5122,6 @@
     public boolean retrieveFromCache()
             throws DBException {
 
-        CacheManager.getInstance();
         CacheSystem cs = CacheManager.getCacheSystem(getDataContext());
         if (cs == null) {
             return false;
@@ -5111,7 +5138,7 @@
 
             if (isCached()) {
                 if (myConfig.dbStats()) {
-                    String statName = myClassName + "|" + getDataContext();
+                    String statName = getStatisticsName();
                     synchronized (sCacheStats) {
                         CacheStatEntry ce = (CacheStatEntry) sCacheStats.get(statName);
 
@@ -5132,11 +5159,13 @@
                 }
 
                 if (cs.existsCache(myClassName)) {
-                    DBObject cachedObject = (DBObject) cs.getItem(myClassName,
-                            getKey());
+                    DBObject cachedObject = (DBObject) cs.getItem(myClassName, getKey());
 
                     if (cachedObject != null) {
                         JDBCObjectMetaData metadata = getJDBCMetaData();
+
+                        // have found cache; now copy fields, with special
+                        // case for when we only want certain fields
                         if (anyFieldsToRetrieve) {
                             String oneFieldName = null;
 
@@ -5162,25 +5191,12 @@
                                 }
                             }
                         } else { /* for each key field */
-
-                            for (Iterator it = metadata.getAllFieldsMap().values().iterator();
-                                 it.hasNext();) {
-                                oneField = (DBField) it.next();
-                                String fieldName = oneField.getName();
-
-                                if (!oneField.isVirtual() && !oneField.isBinaryObjectType() && !oneField.isLongBinaryType()) {
-                                    set(fieldName, cachedObject.getDataField(fieldName)
-                                            .asString());
-                                } else if (oneField.isLongBinaryType()) {
-                                    set(fieldName, cachedObject.getFieldByteArray(fieldName));
-                                }
-                            } /* for each field */
-
+                            copyAllFields(cachedObject);
                         } /* if anyFieldsToRetrieve */
 
 
                         // we know that 'current' values are now the baseline for comparison
-                        cacheIsChangedComparison();
+                        updateIsChanged();
 
                         //
                         //Set it to current since the Cache contains
@@ -5196,7 +5212,7 @@
 
             if (myConfig.dbStats()) {
                 synchronized (sCacheStats) {
-                    String statName = myClassName + "|" + getDataContext();
+                    String statName = getStatisticsName();
 
                     CacheStatEntry ce = (CacheStatEntry) sCacheStats.get(statName);
 
@@ -5215,6 +5231,100 @@
         return false;
     } /* retrieveFromCache() */
 
+    private String getStatisticsName() {
+        return myClassName + "|" + getDataContext();
+    }
+
+    /**
+     * copy all fields from srcObject into this one.  Assumes that the srcObject
+     * has same field naming and type as this one.
+     */
+    protected void copyAllFields(DBObject srcObject) throws DBException {
+        JDBCObjectMetaData metadata = getJDBCMetaData();
+        DBField oneField;
+
+        for (Iterator i = metadata.getAllFieldsMap().values().iterator(); i.hasNext();) {
+            oneField = (DBField) i.next();
+
+            if (!oneField.isVirtual() && !oneField.isBinaryObjectType()) {
+                DataField df = DefaultDataField.getInstance(oneField, this);
+                df.setValue(srcObject.getDataField(oneField.getName()).getValue());
+                setDataField(oneField.getName(), df);
+            }
+        } /* for each field */
+    }
+
+    /**
+     * Creates and returns a copy of this object.  The precise meaning
+     * of "copy" may depend on the class of the object. The general
+     * intent is that, for any object <tt>x</tt>, the expression:
+     * <blockquote>
+     * <pre>
+     * x.clone() != x</pre></blockquote>
+     * will be true, and that the expression:
+     * <blockquote>
+     * <pre>
+     * x.clone().getClass() == x.getClass()</pre></blockquote>
+     * will be <tt>true</tt>, but these are not absolute requirements.
+     * While it is typically the case that:
+     * <blockquote>
+     * <pre>
+     * x.clone().equals(x)</pre></blockquote>
+     * will be <tt>true</tt>, this is not an absolute requirement.
+     * <p/>
+     * By convention, the returned object should be obtained by calling
+     * <tt>super.clone</tt>.  If a class and all of its superclasses (except
+     * <tt>Object</tt>) obey this convention, it will be the case that
+     * <tt>x.clone().getClass() == x.getClass()</tt>.
+     * <p/>
+     * By convention, the object returned by this method should be independent
+     * of this object (which is being cloned).  To achieve this independence,
+     * it may be necessary to modify one or more fields of the object returned
+     * by <tt>super.clone</tt> before returning it.  Typically, this means
+     * copying any mutable objects that comprise the internal "deep structure"
+     * of the object being cloned and replacing the references to these
+     * objects with references to the copies.  If a class contains only
+     * primitive fields or references to immutable objects, then it is usually
+     * the case that no fields in the object returned by <tt>super.clone</tt>
+     * need to be modified.
+     * <p/>
+     * The method <tt>clone</tt> for class <tt>Object</tt> performs a
+     * specific cloning operation. First, if the class of this object does
+     * not implement the interface <tt>Cloneable</tt>, then a
+     * <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays
+     * are considered to implement the interface <tt>Cloneable</tt>.
+     * Otherwise, this method creates a new instance of the class of this
+     * object and initializes all its fields with exactly the contents of
+     * the corresponding fields of this object, as if by assignment; the
+     * contents of the fields are not themselves cloned. Thus, this method
+     * performs a "shallow copy" of this object, not a "deep copy" operation.
+     * <p/>
+     * The class <tt>Object</tt> does not itself implement the interface
+     * <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object
+     * whose class is <tt>Object</tt> will result in throwing an
+     * exception at run time.
+     *
+     * @return a clone of this instance.
+     * @throws CloneNotSupportedException if the object's class does not
+     *                                    support the <code>Cloneable</code> interface. Subclasses
+     *                                    that override the <code>clone</code> method can also
+     *                                    throw this exception to indicate that an instance cannot
+     *                                    be cloned.
+     * @see Cloneable
+     */
+    public Object clone() throws CloneNotSupportedException {
+        DBObject result = null;
+        try {
+            result = newInstance();
+            result.setDataContext(getDataContext());
+            result.copyAllFields(this);
+        } catch (DBException e) {
+            getLogger().error("Problem cloning: ", e);
+            throw new CloneNotSupportedException(e.getMessage());
+        }
+
+        return result;
+    }
 
     /**
      * Find a set of keys of all of the objects that match the current
@@ -5442,8 +5552,8 @@
 
 
     /**
-     * Set an attribute. Attributes are temporary (e.g. not stored in the DBMS) values
-     * associated with this particular DB object instance.
+     * Set an attribute. Attributes are temporary (i.e., not stored in the DBMS) values
+     * associated with this particular object instance.
      *
      * @param attribName  The name of the attribute being defined
      * @param attribValue The object to store under this attribute name
@@ -5787,6 +5897,8 @@
      */
     public synchronized void setField(String fieldName, byte[] fieldValue)
             throws DBException {
+        checkMutable();
+
         StringUtil.assertNotBlank(fieldName, "Field name may not be blank");
 
         if (getStatus() == null) {
@@ -5945,6 +6057,7 @@
      */
     public synchronized void setField(String fieldName, String fieldValue)
             throws DBException {
+        checkMutable();
         StringUtil.assertNotBlank(fieldName, "Field name may not be blank");
 
         if (getStatus() == null) {
@@ -6036,7 +6149,7 @@
      * @param fieldValue the Java date value to set
      * @throws DBException upon error.
      */
-    public void setField(String fieldName, java.util.Date fieldValue)
+    public void setField(String fieldName, Date fieldValue)
             throws DBException {
         DataFieldMetaData fieldMetadata = getFieldMetaData(fieldName);
         if (fieldMetadata.isDateOnlyType()) {
@@ -6101,8 +6214,8 @@
 
 
     /**
-     * Helper function that doesn't fire all the processing... just set's the
-     * field data in raw form and forgets it.
+     * Helper function that avoids all the processing associated with listeners... just set's the
+     * field data in raw form
      *
      * @param fieldName  The name of the field to set
      * @param fieldValue the value to set it to.
@@ -6170,9 +6283,9 @@
 
     /**
      * Convenience method to set the fields to be retrieved
-     * within this database object.
      * <b>NOTE AS OF EXPRESSO 5.0</b> setFieldsToRetrieve() could cause you
-     * some problems if you're attempting to retrieve long objects since
+     * some problems if you're attempting to retrieve objects that have fields of
+     * type isBinaryObject or isLongObjectType since
      * everything will be converted to a STRING.  Please use caution :).
      *
      * @param fieldNames contain the name of the fields separate by "|"
@@ -6222,8 +6335,7 @@
     } /* setFieldsToRetrieve(String) */
 
     /**
-     * Convenience method to set the fields to be input for add and update order
-     * within this database object.
+     * Convenience method to set the fields to be input that become inputs during add and update
      * <b>NOTE AS OF EXPRESSO 5.0</b> setFieldsToInput() could cause you
      * some problems if you're attempting to input long objects since
      * everything will be converted to a STRING.  Please use caution :).
@@ -6285,6 +6397,7 @@
      * @param keyValues The string containing one value for each key
      *                  field in the object
      * @throws DBException If the key fields cannot be set
+     * @deprecated 2/05 v5.6, use setKey() instead with standard delimiter (|) same as getKey()
      */
     public synchronized void setKeys(String keyValues)
             throws DBException {
@@ -6312,6 +6425,44 @@
         }
     } /* setKeys(String) */
 
+    /**
+     * Set the values for each of the key fields of this object from
+     * a |-delimited string (same delimiter as getKey()).
+     * Make sure this delimiter is impossible within a key field
+     *
+     * @see #getKey()
+     * @param keyValues The string containing one value for each key
+     *                  field in the object
+     * @throws DBException If the key fields cannot be set
+     */
+    public synchronized void setKey(String keyValues)
+            throws DBException {
+        checkMutable();
+
+        StringTokenizer stk = new StringTokenizer(keyValues, "|");
+        ArrayList keysToSet = new ArrayList();
+
+        while (stk.hasMoreTokens()) {
+            keysToSet.add(stk.nextToken());
+        }
+
+        ArrayList keys = getJDBCMetaData().getKeyFieldListArray();
+
+        if (keys.size() != keysToSet.size()) {
+            throw new DBException("(" + myClassName + ") There are " +
+                    keys.size() +
+                    " key fields in this table, but " +
+                    keyValues + " only contains " +
+                    keysToSet.size() + " values");
+        }
+
+        Iterator l = keysToSet.iterator();
+
+        for (Iterator i = keys.iterator(); i.hasNext();) {
+            setField((String) i.next(), (String) l.next());
+        }
+    } /* setKeys(String) */
+
 
     /**
      * Set a field's lookup object - this is the name of another database
@@ -6407,7 +6558,7 @@
 
 
     /**
-     * Set the name of this object - this name is used to identify the db object
+     * Set the name of this (static) class - this name is used to identify the db object
      * with a more human-readable description
      *
      * @param theName New name for this object
@@ -6713,6 +6864,8 @@
      */
     public void update(boolean updateChangedFieldsOnly)
             throws DBException {
+        checkMutable();
+
         getExecutor().update(this, updateChangedFieldsOnly);
 
         /* Now log the change if we are logging */
@@ -6755,9 +6908,6 @@
             myUpdates = null;
         } /* if */
 
-
-        // after update, we know that 'current' values are now the baseline for comparison
-        cacheIsChangedComparison();
         setStatus(BaseDataObject.STATUS_CURRENT);
         notifyListeners(EVENT_UPDATE);
     } /*  update() */
@@ -7316,6 +7466,12 @@
      */
     public void set(String fieldName, Object o) throws DataException {
         try {
+            checkMutable();
+        } catch (DBException e) {
+            throw new DataException(e);
+        }
+
+        try {
             //
             // this is a backwards compatability hack
             //
@@ -7357,6 +7513,12 @@
      * @since Expresso 5.0
      */
     public void setDataField(String fieldName, DataField o) throws DataException {
+        try {
+            checkMutable();
+        } catch (DBException e) {
+            throw new DataException(e);
+        }
+
         if (fieldData == null) {
             fieldData = new HashMap();
         }
@@ -7424,6 +7586,10 @@
         }
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // embedded objects
+    /////////////////////////////////////////////////////////////////////////
+
     /**
      * Private class that defines errors for fields.
      */
@@ -7515,10 +7681,6 @@
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // embedded object
-    /////////////////////////////////////////////////////////////////////////
-
     /**
      * Private class used to track field updates
      */
@@ -7538,6 +7700,10 @@
 
     } /* FieldUpdate */
 
+    ///////////////////////////////////////////////////////////////////////////
+    // end embedded objects
+    /////////////////////////////////////////////////////////////////////////
+
     /**
      * set string filters to the given filter on ALL fields that are quoted text fields
      *
@@ -7657,25 +7823,160 @@
         return result;
     }
 
+    /**
+     * set the number of cache instances allowed for this object type. This setting
+     * affects all instances -- it is a class setting, typically used during
+     * setupFields(). This setting automatically persists a record to the DB; no need to
+     * call update after calling this.
+     * author Larry Hamel, CodeGuild, Inc.
+     * @param maxNumInstances maximum number of instances to cache,
+     */
+    public void setCacheLimit(int maxNumInstances) throws DBException {
+        DBObjLimit dbl = new DBObjLimit(SecuredDBObject.SYSTEM_ACCOUNT);
+        dbl.setField(DBObjLimit.DB_OBJECT_NAME, myClassName);
+        boolean found = dbl.find();
+        if ( !found ) {
+            dbl.setField(DBObjLimit.CACHE_SIZE, maxNumInstances);
+            dbl.setField(DBObjLimit.PAGE_LIMIT, "30");
+            dbl.setField(DBObjLimit.TTL, "10");
+            dbl.add();
+        } else {
+            dbl.setField(DBObjLimit.CACHE_SIZE, maxNumInstances);
+            dbl.update();
+        }
+    }
 
     /**
      * set the number of cache instances allowed for this object type. This setting
      * affects all instances -- it is a class setting, typically used during
-     * setupFields()
+     * setupFields(). This setting automatically persists a record to the DB; no need to
+     * call update after calling this.
      * author Larry Hamel, CodeGuild, Inc.
+     * @param maxNumInstances maximum number of instances to cache,
+     * @param minutesToLive TTL for each instance in cache
      */
-    public void setCacheLimit(int numInstances) throws DBException {
+    public void setCacheLimit(int maxNumInstances, int minutesToLive) throws DBException {
         DBObjLimit dbl = new DBObjLimit(SecuredDBObject.SYSTEM_ACCOUNT);
-        dbl.setField("DBObjectName", myClassName);
+        dbl.setField(DBObjLimit.DB_OBJECT_NAME, myClassName);
         boolean found = dbl.find();
         if ( !found ) {
-            dbl.setField("CacheSize", numInstances);
-            dbl.setField("PageLimit", "0");
-            dbl.setField("TTL", "10");
+            dbl.setField(DBObjLimit.CACHE_SIZE, maxNumInstances);
+            dbl.setField(DBObjLimit.PAGE_LIMIT, "30");
+            dbl.setField(DBObjLimit.TTL, minutesToLive);
             dbl.add();
         } else {
-            dbl.setField("CacheSize", numInstances);
+            dbl.setField(DBObjLimit.CACHE_SIZE, maxNumInstances);
+            dbl.setField(DBObjLimit.TTL, minutesToLive);
             dbl.update();
         }
+    }
+
+    /**
+     * a rough estimate on object size in RAM.  NOT suitable for exact calculation
+     *
+     * @return a rough estimate of object size in bytes
+     */
+    public long getSizeEstimate() throws DBException {
+        long size = 0;
+        for (Iterator iterator = getDef().getAllFieldsIterator(); iterator.hasNext();) {
+            DBField f = (DBField) iterator.next();
+            if (f.isVirtual()) {
+                // no op
+            } else if (f.isLongBinaryType()) {
+                byte[] bytes = getFieldByteArray(f.getName());
+                if (bytes != null) {
+                    size += bytes.length;
+                }
+            } else if (!f.isBinaryObjectType()) {
+                DataField df = getDataField(f.getName());
+                if (df != null) {
+                    Object obj = df.getValue();
+                    if (obj != null) {
+                        size += obj.toString().length() * 2; // assumes 2 bytes per char
+                    }
+                }
+            }
+        }
+
+        size += 20 * 4; // each dbobject has about 20 member variables as of 2/05; assume 4 byte references
+        return size;
+    }
+
+    /**
+     * set attribute flag whether this object can be changed--make it immutable.
+     */
+    public void isMutable(boolean shouldBeChangeable) {
+        if (shouldBeChangeable) {
+            if (isMutable()) {
+                // do nothing
+            } else {
+                // default of no setting == true
+                removeAttribute(IS_MUTABLE);
+            }
+        } else {
+            if (!isMutable()) {
+                // do nothing
+            } else {
+                setAttribute(IS_MUTABLE, "false");
+            }
+        }
+    }
+
+    /**
+     * get attribute flag whether this object can be changed--whether this object is mutable;
+     * not saved to disk.  defaults to true;
+     */
+    public boolean isMutable() {
+        boolean result = true;  // default to true
+        String attrib = (String) getAttribute(IS_MUTABLE);
+        if (attrib != null) {
+            result = StringUtil.toBoolean(attrib);
+        }
+        return result;
+    }
+
+    /**
+     * call this before changing data.
+     * check for whether object is mutable.
+     */
+    protected void checkMutable() throws DBException {
+        if (!isMutable()) {
+            throw new DBException(
+                    "Object marked as immutable: this change not allowed for object with key: "
+                    + forKey());
+        }
+    }
+
+    /**
+     * Retrieve a thread local instance of the Perl5 pattern matcher.  Allows
+     * for optimization of # of instances of pattern matcher vs synchronization.
+     *
+     * @return PatternMatcher
+     */
+    protected PatternMatcher getPatternMatcher() {
+        return (PatternMatcher) patternMatcher.get();
+    }
+
+    /**
+     * get actual cached object, which is flagged to be immutable.  efficient for read-only
+     * situations where you want to avoid making a new copy of the object.
+     *
+     * @see DBObject#getKey()
+     * @param dbobjClass class of desired DBObject
+     * @param keyValues String combination of all PK values (see getKey())
+     */
+    public static DBObject getImmutableCachedObject(Class dbobjClass, String keyValues) {
+        return cacheUtils.getImmutableCachedObject(dbobjClass, keyValues);
+    }
+
+    /**
+     * get actual cached object, which is flagged to be immutable.  efficient for read-only
+     * situations where you want to avoid making a new copy of the object.
+     *
+     * @see DBObject#getKey()
+     * @param keyValues String combination of all PK values (see getKey())
+     */
+    public DBObject getImmutableObjectFromCache(String keyValues) {
+        return cacheUtils.getImmutableCachedObject(getClass(), keyValues);
     }
 } // dbobject


More information about the cvs mailing list