KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > core > dbobj > DBObject


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 package com.jcorporate.expresso.core.dbobj;
66
67 import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
68 import com.jcorporate.expresso.core.cache.CacheException;
69 import com.jcorporate.expresso.core.cache.CacheManager;
70 import com.jcorporate.expresso.core.cache.CacheSystem;
71 import com.jcorporate.expresso.core.cache.Cacheable;
72 import com.jcorporate.expresso.core.controller.Transition;
73 import com.jcorporate.expresso.core.dataobjects.BaseDataObject;
74 import com.jcorporate.expresso.core.dataobjects.DataException;
75 import com.jcorporate.expresso.core.dataobjects.DataField;
76 import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
77 import com.jcorporate.expresso.core.dataobjects.DataObject;
78 import com.jcorporate.expresso.core.dataobjects.DataObjectMetaData;
79 import com.jcorporate.expresso.core.dataobjects.DataTransferObject;
80 import com.jcorporate.expresso.core.dataobjects.DefaultDataField;
81 import com.jcorporate.expresso.core.dataobjects.jdbc.FieldRangeParser;
82 import com.jcorporate.expresso.core.dataobjects.jdbc.JDBCDataObject;
83 import com.jcorporate.expresso.core.dataobjects.jdbc.JDBCObjectMetaData;
84 import com.jcorporate.expresso.core.dataobjects.jdbc.JDBCUtil;
85 import com.jcorporate.expresso.core.db.DBConnection;
86 import com.jcorporate.expresso.core.db.DBConnectionPool;
87 import com.jcorporate.expresso.core.db.DBException;
88 import com.jcorporate.expresso.core.db.exception.DBRecordNotFoundException;
89 import com.jcorporate.expresso.core.misc.ConfigJdbc;
90 import com.jcorporate.expresso.core.misc.ConfigManager;
91 import com.jcorporate.expresso.core.misc.ConfigurationException;
92 import com.jcorporate.expresso.core.misc.DateTime;
93 import com.jcorporate.expresso.core.misc.StringUtil;
94 import com.jcorporate.expresso.core.registry.RequestRegistry;
95 import com.jcorporate.expresso.core.security.filters.Filter;
96 import com.jcorporate.expresso.kernel.util.ClassLocator;
97 import com.jcorporate.expresso.kernel.util.FastStringBuffer;
98 import com.jcorporate.expresso.services.dbobj.ChangeLog;
99 import com.jcorporate.expresso.services.dbobj.DBObjLimit;
100 import com.jcorporate.expresso.services.dbobj.Setup;
101 import org.apache.log4j.Logger;
102 import org.apache.oro.text.regex.Pattern;
103 import org.apache.oro.text.regex.PatternMatcher;
104 import org.apache.oro.text.regex.Perl5Matcher;
105
106 import java.io.IOException JavaDoc;
107 import java.io.InputStream JavaDoc;
108 import java.io.ObjectInputStream JavaDoc;
109 import java.io.ObjectOutputStream JavaDoc;
110 import java.lang.reflect.Method JavaDoc;
111 import java.math.BigDecimal JavaDoc;
112 import java.sql.SQLException JavaDoc;
113 import java.text.DecimalFormat JavaDoc;
114 import java.text.NumberFormat JavaDoc;
115 import java.text.ParseException JavaDoc;
116 import java.util.ArrayList JavaDoc;
117 import java.util.Collections JavaDoc;
118 import java.util.Enumeration JavaDoc;
119 import java.util.HashMap JavaDoc;
120 import java.util.Iterator JavaDoc;
121 import java.util.List JavaDoc;
122 import java.util.Locale JavaDoc;
123 import java.util.Map JavaDoc;
124 import java.util.StringTokenizer JavaDoc;
125 import java.util.Vector JavaDoc;
126
127
128 /**
129  * <p>DBObjects are the core of Expresso's object-relational mapping. They are object-oriented
130  * wrappers for some sort of data. They are generally expected to be used in a single thread, like
131  * in processing a web query.</p>
132  * <p>When making your own application, you derive your classes from DBObject or
133  * SecuredDBObject.</p>
134  *
135  * @author Michael Nash
136  * @see com.jcorporate.expresso.core.dbobj.SecuredDBObject
137  * @see com.jcorporate.expresso.core.db.DBException
138  * @see com.jcorporate.expresso.core.db.DBConnection
139  * @since Expresso 1.0
140  */

141 public abstract class DBObject
142         extends JDBCDataObject
143         implements Cacheable, LookupInterface {
144
145
146     /**
147      * setup code for update() policy:
148      * for efficiency, developers who are confident that their code does not have any
149      * 'blind updates' can set the Setup value UPDATE_CHANGED_ONLY to true
150      * (a blind update is where the object is not retieved before values in it are reset)
151      */

152     public static final String JavaDoc UPDATE_CHANGED_ONLY = "UPDATE_CHANGED_ONLY";
153
154     /**
155      * Attribute String for if there's an error with the field.
156      */

157     public static final String JavaDoc ATTRIBUTE_ERROR = "error";
158
159     /**
160      * Attribute String for what message to display if there's an error
161      * with thie field.
162      */

163     public static final String JavaDoc ATTRIBUTE_ERROR_MESSAGE = "error-message";
164
165     /**
166      * Attribute for what is the limit to retrieve from searchAndRetrieve operations
167      */

168     public static final String JavaDoc ATTRIBUTE_PAGE_LIMIT = "pageLimit";
169
170     /**
171      * Event 'Add' code
172      */

173     public static final String JavaDoc EVENT_ADD = "A";
174
175     /**
176      * Event 'Delete' Code
177      */

178     public static final String JavaDoc EVENT_DELETE = "D";
179
180     /**
181      * Event 'Update' Code
182      */

183     public static final String JavaDoc EVENT_UPDATE = "U";
184
185
186     /**
187      * A static zero BIG DECIMAL object
188      * <p/>
189      * author Peter Pilgrim <peterp@xenonsoft.demon.co.uk>
190      *
191      * @see #getFieldBigDecimal
192      */

193     transient protected static final BigDecimal JavaDoc BIG_DECIMAL_ZERO = new BigDecimal JavaDoc(0.0);
194
195
196     /* the logs
197     * @todo move this static variable outside DBObject so we can wrap it for
198     * EJBs
199     */

200     transient private static Logger log = Logger.getLogger(DBObject.class);
201
202
203     /**
204      * 30 minute DBObjLimit cache tty
205      */

206     transient private static final long DBOBJLIMIT_CACHE_TTL = 60 * 1000 * 30;
207
208     /**
209      * Statistics for Cache entries.
210      */

211     transient private static ConcurrentReaderHashMap sCacheStats = new ConcurrentReaderHashMap();
212
213
214     /**
215      * Cache Util object for bridging DBObject &lt;-&gt; CacheManager
216      */

217     transient final private static CacheUtils cacheUtils = new CacheUtils();
218
219     /**
220      * A Pattern Matcher for examining fields vs validation regular expressions
221      * It has been modified for thead local instantiation to reduce synchronization.
222      */

223     transient private static ThreadLocal JavaDoc patternMatcher = new ThreadLocal JavaDoc() {
224         protected synchronized Object JavaDoc initialValue() {
225             return new Perl5Matcher();
226         }
227     };
228     private static Logger slog = null;
229     public static final String JavaDoc WHERE_KEYWORD = " WHERE ";
230
231     /**
232      * Retrieve a thread local instance of the Perl5 pattern matcher. Allows
233      * for optimization of # of instances of pattern matcher vs synchronization.
234      *
235      * @return PatternMatcher
236      */

237     protected PatternMatcher getPatternMatcher() {
238         return (PatternMatcher) patternMatcher.get();
239     }
240
241
242     /**
243      * Field Range Parser verifies range values to make sure there isn't things
244      * like SQL injection getting passed into the range.
245      */

246     transient private static FieldRangeParser rangeVerifier = new FieldRangeParser();
247
248
249 ///////////////////////////////////////////////////////////////////////////////////////
250
// instance vars
251
///////////////////////////////////////////////////////////////////////////////////////
252

253     /**
254      * string filter class; null defaults to mean HtmlFilter
255      * this should be stored here, in the object instance, rather than a setting
256      * in metadata for all objects of this type: consider a use-case where
257      * one object is getting rendered in HTML while another is simultaneously
258      * rendered in XML.
259      */

260     private Class JavaDoc mFilter = null;
261
262     /**
263      * A DBObject instance often refers to a single row, and this hash map contains
264      * the row data for this instance. This Map specifically contains a list of
265      * <code>DataField</code> objects that in turn contain the actual field data.
266      */

267     private Map fieldData = null;
268
269
270     /**
271      * Contains a map of DBObject.FieldError classes describing the errors
272      * set by checkField().
273      */

274     private HashMap fieldErrors = null;
275
276
277     /* This is the locale of the DBObject. Originally this attribute
278      * was part of the subclass <code>SecuredDBObject</code>. However
279      * there is no reason that explains why all DBObjects should not
280      * support internationalisation (i18n).
281      * Modification by Peter Pilgrim Wed Jan 01 18:27:14 GMT 2003
282      *
283      * @see #getLocale
284      * @see #setLocale( Locale )
285      */

286     private Locale JavaDoc myLocale = Locale.getDefault();
287
288
289     /**
290      * Keys that have been found in the last retrieve.
291      */

292     private ArrayList JavaDoc foundKeys = null;
293
294     /**
295      * Attributes of this DB Object
296      */

297     private HashMap attributes = null;
298
299     /* The cache size of this particular DB object */
300     private int myCacheSize = -2;
301
302
303     /**
304      * Very Similar to "anyFieldsToRetrieve" already present on the DBObject.
305      * author ABHI
306      */

307     boolean anyFieldsToRetrieveMulti = false;
308
309
310     /**
311      * Integer Regular Expression for easy reference
312      */

313     public static final String JavaDoc INT_MASK = "^[+-]?[0-9]+";
314
315     /**
316      * Floating point regular expression syntax for easy reference.
317      */

318     public static final String JavaDoc FLOAT_MASK = "^([+-]?)(?=\\d|\\.\\d)\\d*(\\.\\d*)?([Ee]([+-]?\\d+))?$";
319
320     /**
321      * Email Regular Expression Constant.
322      */

323     public static final String JavaDoc EMAIL_MASK =
324             "^[A-Za-z0-9\\-\\.\\_]+"
325             + "@"
326             + "[A-Za-z0-9\\-\\.\\_]+"
327             + "\\."
328             + "[a-zA-Z]{2,6}$"; // 6 chars in "museum" TLD
329
public static final String JavaDoc IS_CHECK_RELATIONAL_INTEGRITY = "isCheckRelationalIntegrity";
330
331     /**
332      * Default Constructor. This allows a DB object to be dynamically
333      * instantiated (e.g. loaded
334      * with Class.forName()) and does all of the required initializations.
335      *
336      * @throws DBException upon error.
337      */

338     public DBObject()
339             throws DBException {
340         initialize();
341     } /* DBObject() */
342
343     /**
344      * Constructor that sets a connection as the object is created - typically
345      * this is used when a particular DBConnection is required for the purposes of
346      * maintaining a database transaction. If a specific connection is not used,
347      * there is no way to use commit() and rollback() in the event of failure, as a
348      * different DBConnection might be used for each phase of the transaction.
349      * Critial sections should therefore explicity request a DBConnection from the
350      * connection pool and pass it to each of the DB objects in that section.
351      *
352      * @param newConnection The DBConnection to utilize
353      * @throws DBException upon error.
354      */

355     public DBObject(DBConnection newConnection)
356             throws DBException {
357         this(newConnection, newConnection.getDataContext());
358     } /* DBObject(DBConnection) */
359
360
361     /**
362      * Constructor that sets a connection as the object is created - typically
363      * this is used when a particular DBConnection is required for the purposes of
364      * maintaining a database transaction. If a specific connection is not used,
365      * there is no way to use commit() and rollback() in the event of failure, as a
366      * different DBConnection might be used for each phase of the transaction.
367      * Critial sections should therefore explicity request a DBConnection from the
368      * connection pool and pass it to each of the DB objects in that section.
369      * <p>This constructor is neceesary to work with otherDBMap and transaction
370      * capabilities</p>
371      *
372      * @param newConnection The DBConnection to utilize
373      * @param setupTablesContext The data context that contains the setup (and
374      * security) tables for this object
375      * @throws DBException upon error.
376      * @since Expresso 5.0.1
377      */

378     public DBObject(DBConnection newConnection, String JavaDoc setupTablesContext)
379             throws DBException {
380         this();
381         setConnection(newConnection, setupTablesContext);
382     } /* DBObject(DBConnection) */
383
384     /**
385      * For using DBObjects within Controllers. Initializes based upon
386      * the current locale and the requested db context. There is no
387      * current user login id set in this method. If you need the user id
388      * then use you should use the subclass <code>SecuredDBObject</code>
389      * type instead.
390      *
391      * @param request - The controller request handed to you by the framework.
392      * @throws DBException if there's an error constructing the SecuredDBObject
393      */

394     public DBObject(RequestContext request)
395             throws DBException {
396         this();
397         setDataContext(request.getDBName());
398         setLocale(request.getLocale());
399     }
400
401     /**
402      * Get the current locale for this dbobject
403      *
404      * @return The currently set locale or null if there is no locale set.
405      * @since Expresso 5.0.1
406      */

407     public Locale JavaDoc getLocale() {
408         return myLocale;
409     }
410
411     /**
412      * Sets the locale to be used with this DBObject
413      *
414      * @param newLocale The new Locale to use with this object
415      * @since Expresso 5.0.1
416      */

417     public void setLocale(Locale JavaDoc newLocale) {
418         myLocale = newLocale;
419     }
420
421     /**
422      * Initialize this DBObject and set the db/context to the specified key
423      *
424      * @param newdbKey The database Context name
425      * @throws DBException upon error.
426      */

427     public DBObject(String JavaDoc newdbKey)
428             throws DBException {
429         this();
430         setDataContext(newdbKey);
431     } /* DBObject(String) */
432
433     /**
434      * Add a new record to the target table.
435      * Assumes that the fields of this object are populated with data for the new
436      * record. All key fields at least must be supplied with values, and all fields
437      * that are specified as "no nulls". This method also validates all referential
438      * integrity constraints specified by the object.
439      *
440      * @throws DBException If the record cannot be added - this includes if the
441      * record has a duplicate key
442      */

443     public void add()
444             throws DBException {
445
446         getExecutor().add(this);
447
448         /* Now log the change if we are logging */
449         if (getDef().isLoggingEnabled()) {
450             ChangeLog myChangeLog = new ChangeLog();
451             myChangeLog.setDataContext(getDataContext());
452             myChangeLog.setField("ObjectChanged", myClassName);
453             myChangeLog.setField("RecordKey", getMyKeys());
454             myChangeLog.setField("Operation", EVENT_ADD);
455             myChangeLog.setField("ChangedField", "ALL");
456             myChangeLog.add();
457
458             /* We're done tracking changes */
459             myUpdates = null;
460         } /* if */
461
462         // after add(), we know that 'current' values are now the baseline for comparison
463
cacheIsChangedComparison();
464         setStatus(BaseDataObject.STATUS_CURRENT);
465         notifyListeners(EVENT_ADD);
466     } /* add() */
467
468     /**
469      * Hand a dbobject a connection that contains fields corresponding to
470      * what the dbobject expects, and it'll set itself.
471      * <p/>
472      * Does not increment the result set in the DBConnection.
473      *
474      * @param connection The connection that currently has a dbobject ready to
475      * be read in it's result set.
476      * @return The number of fields read. Depending on the SQL you sent to the connection
477      * the DBObject might not have all fields in existence.
478      * @throws DBException upon error.
479      */

480     public synchronized int loadFromConnection(DBConnection connection)
481             throws DBException {
482         String JavaDoc oneFieldName = null;
483         Object JavaDoc tmpData = null;
484         int fieldCount = 0;
485         JDBCObjectMetaData metadata = getJDBCMetaData();
486         for (Iterator JavaDoc it = metadata.getFieldListArray().iterator(); it.hasNext();) {
487             oneFieldName = (String JavaDoc) it.next();
488
489             try {
490                 DataFieldMetaData oneField = metadata.getFieldMetadata(oneFieldName);
491                 if (oneField.isDateType()) {
492                     tmpData = getCustomStringFieldValue(connection, oneFieldName);
493                 } else {
494                     if (!oneField.isLongBinaryType() && !oneField.isLongCharacterType()) {
495                         if (connection.isStringNotTrim()) {
496                             tmpData = connection.getStringNoTrim(oneFieldName);
497                         } else {
498                             tmpData = connection.getString(oneFieldName);
499                         }
500                     } else {
501                         if (oneField.isLongBinaryType()) {
502                             tmpData = null;
503                             InputStream JavaDoc is = connection.getBinaryStream(oneFieldName);
504                             if (is != null) {
505                                 byte[] bstr = new byte[LONGBINARY_READ_DEFAULT_SIZE];
506                                 int j = is.read(bstr);
507                                 if (j > 0) {
508                                     byte[] content = new byte[j];
509                                     System.arraycopy(bstr, 0, content, 0, j);
510                                     tmpData = content;
511                                 }
512                             }
513                         } else {
514                             tmpData = connection.getStringNoTrim(oneFieldName);
515                         }
516                     }
517
518 // if (connection.isStringNotTrimmed()) {
519
// oneFieldValue = connection.getStringNoTrim(oneFieldName);
520
// } else {
521
// oneFieldValue = connection.getString(oneFieldName);
522
// }
523
}
524
525 // this.setField(oneFieldName, oneFieldValue);
526
this.set(oneFieldName, tmpData);
527                 fieldCount++;
528             } catch (DBException de) {
529                 if (log.isDebugEnabled()) {
530                     log.debug("Failed to load field.", de);
531                 }
532
533                 //Failed to load this field, that's fine
534
} catch (Exception JavaDoc e) {
535                 if (log.isDebugEnabled()) {
536                     log.debug("Failed to load field.", e);
537                 }
538
539                 //Failed to load this field, that's fine
540
}
541
542         } /* for each retrieved field name */
543
544
545         setDataContext(getDataContext());
546         cacheIsChangedComparison();
547         setStatus(BaseDataObject.STATUS_CURRENT);
548
549         return fieldCount;
550     }
551
552     /**
553      * reset 'original' value and isChanged flags on all fields, establishing a baseline for comparison.
554      * call when add(), retrieve, or update() has occurred, and currentValue of data fields should be
555      * considered 'original value' for purposes of determining 'isChanged'
556      *
557      * @throws DBException upon error.
558      */

559     public void cacheIsChangedComparison() throws DBException {
560         for (Iterator JavaDoc i = getMetaData().getFieldListArray().iterator(); i.hasNext();) {
561             String JavaDoc oneFieldName = (String JavaDoc) i.next();
562             DataField field = getDataField(oneFieldName);
563             field.cacheIsChangedComparison();
564         }
565     }
566
567
568     /**
569      * This is used internally by JDBC Exceutor's and JDBC Query when dealing
570      * with queries to the underlying datasource. Under normal conditions you
571      * would not used this function directly.
572      *
573      * @param fieldName the name of the fieldname found.
574      */

575     public void addFoundKeys(String JavaDoc fieldName) {
576         if (foundKeys == null) {
577             foundKeys = new ArrayList JavaDoc();
578         }
579
580         foundKeys.add(fieldName);
581     }
582
583     /**
584      * Specify a new "detail" db object, and the fields in this object
585      * they specify the fields in the related object
586      *
587      * @param objName The class name of the related object. There is assumed to be
588      * a one to one or one to many relationship from this object to the specified object
589      * @param keyFieldsLocal A pipe-delimited list of field names in this object
590      * @param keyFieldsForeign A pipe-delimieted list of field names in the other object
591      * @throws DBException upon error.
592      */

593     protected synchronized void addDetail(String JavaDoc objName,
594                                           String JavaDoc keyFieldsLocal,
595                                           String JavaDoc keyFieldsForeign)
596             throws DBException {
597         getDef().addDetail(objName, keyFieldsLocal, keyFieldsForeign);
598     }
599
600     /**
601      * Add a field with more details: This version allows the user to specify a
602      * precision, for fields that use both a size and precision.
603      *
604      * @param fieldName Name of the field
605      * @param fieldType Type of the field - this is the internal Expresso type,
606      * mapping in DBField to a specific database data type.
607      * @param fieldSize Size of the field
608      * @param fieldPrecision The precision of the field
609      * @param allowNull Does this field allow nulls?
610      * @param fieldDescription A longer description of this field
611      * (user-understandable hopefully!)
612      * @throws DBException upon error.
613      */

614     protected synchronized void addField(String JavaDoc fieldName, String JavaDoc fieldType,
615                                          int fieldSize, int fieldPrecision,
616                                          boolean allowNull,
617                                          String JavaDoc fieldDescription)
618             throws DBException {
619         getDef().addField(fieldName, fieldType, fieldSize, fieldPrecision,
620                 allowNull, fieldDescription);
621     } /* addField(String, String, int, int, boolean, String) */
622
623
624     /**
625      * Add a field with more details: This version of addfield supplies
626      * the allowNull flags and a description of the field to be used
627      * when reporting errors to the user. This method is only used by the class that
628      * extends DB object, and typically only in the setupFields() method.
629      *
630      * @param fieldName Name of the field
631      * @param fieldType Type of the field - this is the "internal" Expresso type,
632      * and is mapped to a specific type for the database depending on the
633      * mappings in the properties file (if any). The DBField object contains
634      * the default mappings.
635      * @param fieldSize Size of this field, if specified for this type of field. For
636      * fields that do not use a size (such as "date"), specify 0 for the size.
637      * @param allowNull Does this field allow nulls?
638      * @param fieldDescription A longer description of this field
639      * (user-understandable hopefully!)
640      * @throws DBException upon error.
641      */

642     protected synchronized void addField(String JavaDoc fieldName, String JavaDoc fieldType,
643                                          int fieldSize, boolean allowNull,
644                                          String JavaDoc fieldDescription)
645             throws DBException {
646         getDef().addField(fieldName, fieldType, fieldSize, allowNull,
647                 fieldDescription);
648     } /* addField(String, String, int, boolean, String) */
649
650
651     /**
652      * Determine if a record with this key exists already - if
653      * not, add a new record. Note that this method uses just the key
654      * fields to determine if the record already exists.
655      *
656      * @throws DBException upon error.
657      */

658     public synchronized void addIfNeeded()
659             throws DBException {
660
661         DBObject searchObj = newInstance();
662         searchObj.setDataContext(getDataContext());
663
664         String JavaDoc oneFieldName = null;
665
666         for (Iterator JavaDoc i = getKeyFieldListIterator(); i.hasNext();) {
667             oneFieldName = (String JavaDoc) i.next();
668             DataFieldMetaData oneField = getFieldMetaData(oneFieldName);
669             String JavaDoc value = getField(oneFieldName);
670
671             // warn user if they should not be using this method
672
if (value.length() == 0) {
673                 log.warn(
674                         "a key field is empty, and yet DBObject.addIfNeeded() only uses primary-key fields for search; should you be using searchAndRetrieve() method instead? this addIfNeeded() will add ONLY if there is no other object of this type; after one object, it will always fail.");
675             }
676
677             if (!oneField.isVirtual()) {
678                 searchObj.setField(oneFieldName, value);
679             }
680         }
681         if (!searchObj.find()) {
682             add();
683         }
684     } /* addOrUpdate() */
685
686
687     /**
688      * Use this in your derived checkField() class to add error messages to
689      * be associated with various fields.
690      *
691      * @param fieldName The field name to add the error to
692      * @param errorMessage The custom error message to associate when there's
693      * a problem with this field
694      */

695     protected void addFieldError(String JavaDoc fieldName, String JavaDoc errorMessage) {
696         if (fieldErrors == null) {
697             fieldErrors = new HashMap(5);
698         }
699
700         fieldErrors.put(fieldName, new DBObject.FieldError(fieldName, errorMessage));
701     }
702
703     /**
704      * Retrieve the error message associated with this field.
705      *
706      * @param fieldName the fieldName to get the associated error message
707      * @return A string containing the field error message or NULL if there is
708      * no error for this field. or POSSIBLY if no error message has been set
709      * for this field.
710      */

711     public String JavaDoc getFieldErrorMessage(String JavaDoc fieldName) {
712         if (fieldErrors == null) {
713             return null;
714         }
715
716         DBObject.FieldError fe = (DBObject.FieldError) fieldErrors.get(fieldName);
717         if (fe == null) {
718             return null;
719         }
720
721         return fe.getErrorMessage();
722     }
723
724     /**
725      * Use this to check if a field is in error.
726      *
727      * @param fieldName Check if there's an error set for this field.
728      * @return true if an error is set for this field.
729      */

730     public boolean hasError(String JavaDoc fieldName) {
731         try {
732             DataField df = getDataField(fieldName);
733             if (df.getAttribute(ATTRIBUTE_ERROR) != null) {
734                 return true;
735             }
736         } catch (DBException ex) {
737             log.error("Invalid field name: " + fieldName, ex);
738             return false;
739         }
740
741         if (fieldErrors == null) {
742             return false;
743         }
744
745         DBObject.FieldError fe = (DBObject.FieldError) fieldErrors.get(fieldName);
746         if (fe == null) {
747             return false;
748         }
749
750         return true;
751     }
752
753     /**
754      * Use this to check if any fields are in error.
755      *
756      * @return true if an error is set.
757      */

758     public boolean hasErrors() {
759         if (fieldErrors == null || fieldErrors.isEmpty()) {
760             return false;
761         }
762
763         return true;
764     }
765
766     /**
767      * Used to clear field error flags.
768      *
769      * @param fieldName the name of the field to clear the error flag
770      */

771     protected void clearError(String JavaDoc fieldName) {
772         if (fieldErrors == null) {
773             return;
774         }
775
776         fieldErrors.remove(fieldName);
777     }
778
779     /**
780      * Add an index to the table.
781      *
782      * @param indexName the name to give the index in the table; MUST CONTAIN NO SPACES--use underscores instead
783      * @param fieldNames A comma delimited list of all fields in the index.
784      * @param isUnique - True if this field is a unique index.
785      * @throws IllegalArgumentException of fieldName is null or doesn't exist
786      * or if indexName is null
787      * @throws DBException upon error.
788      */

789     protected void addIndex(String JavaDoc indexName, String JavaDoc fieldNames,
790                             boolean isUnique)
791             throws IllegalArgumentException JavaDoc, DBException {
792         getDef().addIndex(indexName, fieldNames, isUnique);
793     }
794
795     /**
796      * Add a new field to the list of fields that are part of this
797      * object's key. Called after all of the "addField" calls in the setupFields()
798      * method to specify which fields make up the primary key of this object.
799      *
800      * @param keyFieldName The name of the field to add as part of the key
801      * @throws DBException if the field name is not valid or the field
802      * allows nulls
803      */

804     protected synchronized void addKey(String JavaDoc keyFieldName)
805             throws DBException {
806         getDef().addKey(keyFieldName);
807     } /* addKey(String) */
808
809
810     /**
811      * Determine if a record with these fields exists already - if so, update. If
812      * not, add a new record.
813      *
814      * @throws DBException upon error.
815      */

816     public synchronized void addOrUpdate()
817             throws DBException {
818         DBObject searchObj = newInstance();
819         // we must have all key fields for retrieve & update to work
820
boolean canUpdate = true;
821         String JavaDoc oneFieldName = null;
822         JDBCObjectMetaData metadata = getJDBCMetaData();
823         for (Iterator JavaDoc i = metadata.getKeyFieldListArray().iterator(); i.hasNext();) {
824             oneFieldName = (String JavaDoc) i.next();
825             DataField df = getDataField(oneFieldName);
826             if (df == null || df.isNull()) {
827                 canUpdate = false; // we do not have all keys--cannot update
828
break;
829             }
830
831             searchObj.setField(oneFieldName, getField(oneFieldName));
832         }
833
834         if (!canUpdate) {
835             add();
836             return;
837         }
838
839
840         try {
841             searchObj