Source Code Cross Referenced for SRecordInstance.java in  » Database-ORM » SimpleORM » simpleorm » core » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database ORM » SimpleORM » simpleorm.core 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        package simpleorm.core;
002:
003:        import java.sql.*;
004:        import java.math.BigDecimal;
005:
006:        /*
007:         * Copyright (c) 2002 Southern Cross Software Queensland (SCSQ).  All rights
008:         * reserved.  See COPYRIGHT.txt included in this distribution.
009:         */
010:
011:        /** Each SRecordInstance represents an individual record in memory
012:         that either correspond to a row in the database or are a new
013:         record that has yet to be inserted.<p>
014:        
015:         RecordInstances are created by either {@link
016:         SRecordMeta#findOrCreate findOrCreate} or {@link SResultSet#getRecord}.  The
017:         former selects a specific record by primary key, while the latter
018:         retrieves a record from a general query result set.<p>
019:        
020:         The main methods are <code>get*</code> and <code>set*</code> which
021:         get and set field values of the record.  {@link #deleteRecord}
022:         deletes a row. <p>
023:        
024:         No two RecordInstances can have the same primary key field values
025:         within the same connection.  Attempts to retrieve the same row
026:         twice will simple return a pointer to an existing SRecordInstance.
027:         There is no relationship between RecordInstances in different
028:         connections -- all locking is done by the underlying database.<p>
029:        
030:         SRecordsInstances may also be {@link #detach}ed from a connection
031:         and then subsequently {@link #attach}ed to another connection.
032:         Optimistic locking is used.<p>
033:        
034:         ### Serializable, but only naively.  Should throw exception if one
035:         attempts to serialize an attached record, or references to purged
036:         records.  When deserializing, should match meta data with current
037:         SRecordMeta.  The following of links to referenced records should
038:         be manual and should be part of the serialization process and not
039:         the detachment process.<p> 
040:         */
041:
042:        public abstract class SRecordInstance implements  SSerializable
043:        //,java.io.Serializable // J# problems
044:        {
045:
046:            /** This must be defined in every user record's definition to access
047:             the SRecord which provides the meta data for this instance.  It
048:             is normally defined as:-<p>
049:             <pre>  SRecord getMeta() { return meta; }; </pre> <p>
050:             
051:             The actual <code>meta</code> variable is thus not Serialized,
052:             but it would not be anyway as it is usually static. */
053:            public abstract SRecordMeta getMeta();
054:
055:            /** The one connection to which this Instance is glued. Instances
056:             may never be shared across connections.*/
057:            transient SConnection sConnection = null;
058:
059:            /** The actual field values for this record instance.  Null if
060:             destroyed.  Elements null if retrieved data SQL Null. */
061:            protected Object fieldValues[] = new Object[getMeta().sFieldMetas
062:                    .size()];
063:
064:            /** INstance BitSet, one per field instance in fieldValues, in
065:             particular INS_DIRTY which indicates that the field has been
066:             modified.  Values are INS_* */
067:            byte bitSets[] = new byte[getMeta().sFieldMetas.size()];
068:
069:            /** The copy of the fieldValues used for optimistic locking.  null
070:             means not optimistically locked.*/
071:            Object[] optimisticFieldValues = null;
072:
073:            /** True if queried read only.  Ie. either selected FOR UPDATE,
074:             optimistically locked, or new and not read only.  */
075:            boolean readOnly = true;
076:
077:            /** -1: not in sConnection.updateList either because 1. not dirty or
078:             2. not attached.*/
079:            int updateListIndex = -1; // (Transient means set to 0, not -1!)
080:
081:            /** Is dirty and needs to be flushed.  But may not be in
082:             sConnection.updateList because the instance is detached. */
083:            boolean dirty = false;
084:
085:            /** Need to delete at commit. */
086:            boolean deleted = false;
087:
088:            /** Set after <code>findOrInsert()</code> that does not find the row
089:             already in the database. */
090:            boolean newRow = false;
091:
092:            /** @see #wasInCache()
093:             */
094:            boolean wasInCache = false;
095:
096:            /** Should only be called by SimpleORM, but difficult to protect. */
097:            public SRecordInstance() {
098:            }
099:
100:            /**
101:             Determines whehter two SRecordInstances are for the same
102:             SRecordMeta and have the same primary key fields.<p>
103:             
104:             This is purely for the purpose of putting
105:             <code>SRecordInstances</code> in the
106:             <code>SConnection.transactionCache</code>.  This is done as
107:             <code>tc.put(SRecordInstance, SRecordInstance)</code>, ie the
108:             instance is used for both the key and the body.  This avoids
109:             having to create a separate primary key object for each record
110:             instance.<p>
111:             
112:             It only foreign keys, not referenced SRecordInstances which may
113:             be null if the parent record has not been retrieved.  */
114:            public boolean equals(Object key2) {
115:                if (this  == key2)
116:                    return true;
117:                if (!(key2 instanceof  SRecordInstance))
118:                    return false;
119:
120:                SRecordInstance pkey2 = (SRecordInstance) key2;
121:                SRecordMeta meta = getMeta();
122:                if (meta != pkey2.getMeta())
123:                    return false;
124:                for (int kx = 0; kx < meta.keySFieldMetas.size(); kx++) {
125:                    SFieldMeta fmeta = (SFieldMeta) meta.keySFieldMetas.get(kx);
126:                    if (!(fmeta instanceof  SFieldReference)) {
127:                        int keyx = fmeta.fieldIndex;
128:                        Object k1 = this .fieldValues[keyx];
129:                        Object k2 = pkey2.fieldValues[keyx];
130:                        if (!k1.equals(k2))
131:                            return false; // Can never be null
132:                    }
133:                }
134:                return true;
135:            }
136:
137:            /** See <code>equals()</code>.*/
138:            public int hashCode() {
139:                // ## Could cache this calculation.
140:                //SLog.slog.debug("hashCode " + allFields());
141:                SRecordMeta meta = getMeta();
142:                int code = meta.getString(SCon.STABLE_NAME).hashCode();
143:                for (int kx = 0; kx < meta.keySFieldMetas.size(); kx++) {
144:                    SFieldMeta fmeta = (SFieldMeta) meta.keySFieldMetas.get(kx);
145:                    if (!(fmeta instanceof  SFieldReference)) {
146:                        int keyx = fmeta.fieldIndex;
147:                        Object k1 = this .fieldValues[keyx];
148:                        code += k1.hashCode() * ((kx + 1) * 13); // Can never be null
149:                    }
150:                }
151:                return code;
152:            }
153:
154:            /**
155:             Returns the field's value as a Java Object.  Methods such as
156:             <code>getString()</code> dispach to this method but are
157:             generally more convenient because the cast the result to the
158:             correct type.  This method in turn just dispaches to
159:             <code>field.getFieldValue()</code>, ie. it is the declaredtype
160:             of the SField that determines how the value is retrieved.<p> */
161:            public Object getObject(SFieldMeta field) {
162:                Object cacheValue = field.getFieldValue(this , 0);
163:                return cacheValue;
164:            }
165:
166:            /** For references only. */
167:            public Object getObject(SFieldReference field, long sqy_flags) {
168:                Object cacheValue = field.getFieldValue(this , sqy_flags);
169:                return cacheValue;
170:            }
171:
172:            /**
173:             Generic routine to set a fields value.  Just dispaches to
174:             <code>field.setFieldValue()</code><p>
175:             */
176:            public void setObject(SFieldMeta field, Object value) {
177:                //SLog.slog.debug("setObject " + field + " = " + value + ": " + value.getClass());
178:                field.setFieldValue(this , value);
179:            }
180:
181:            public boolean isNull(SFieldMeta field) {
182:                return getObject(field) == null;
183:            }
184:
185:            public void setNull(SFieldMeta field) {
186:                setObject(field, null);
187:            }
188:
189:            /** True if field is the empty value.  Currently just tests
190:             <code>getObject() == null</code>.  But other options will be added
191:             later to allow "" to be treated as null.
192:             @see #SMANDATORY
193:             */
194:            public boolean isEmpty(SFieldMeta field) {
195:                return getObject(field) == null;
196:            }
197:
198:            /** Sets field to be empty, ie. currently <code>setObject(,
199:             null)</code>. But other options will be added later.
200:             @see #SMANDATORY */
201:            public void setEmpty(SFieldMeta field) {
202:                setObject(field, null);
203:            }
204:
205:            /** Gets the value of field cast to a String, trimed of trailing
206:             spaces.  This is equivalent to
207:             <code>getObject().toString().trimTrailingSpaces()</code>.  So
208:             the field type itself need not be <code>SFieldString</code>, but
209:             just something that can be cast to a String.  Trailing spaces
210:             can be a problem with some databases and fileds declared
211:             <code>CHAR</code>.<p>
212:             
213:             Note that if you do not want trailing spaces trimmed, then
214:             just call getObject().toString() manually.
215:             (For CHAR fields, most dbs/jdbc drivers seem to trim, but this is
216:             highly inconsistent.)<p>
217:             
218:             ## Trimming is an issue with CHAR style fields that pad with spaces.
219:             Currently we always read from database into the fields without trimming.
220:             The idea being to let the SimpleORM user get at the raw query result using 
221:             getObject, whatever that raw result is.<p>
222:             
223:             Most DBs seem to trim for us.  But some may not, and some may require the
224:             trailing spaces on queries.  Certainly trailing spaces in the objects will
225:             upset the record cache, and there is some dubious code in SRecordFinder.retrieveRecord()
226:             to handle this.<p>
227:             
228:             I think that for CHAR fields we need to always trim at database read, and pad where
229:             needed.  This should also be dispatched to DB handler.  I think that Oracle gives grief.
230:             (Note that trim means trailing spaces, leading should be left alone.)<p>
231:             
232:             I have not done this because it would require testing on many datbases.<p> 
233:             */
234:            public String getString(SFieldMeta field) {
235:                Object val = getObject(field);
236:                if (val == null)
237:                    return null;
238:                String str = val.toString();
239:                int end = str.length() - 1;
240:                int sx = end;
241:                for (; sx > -1 && str.charAt(sx) == ' '; sx--)
242:                    ;
243:                if (sx != end)
244:                    return str.substring(0, sx + 1);
245:                else
246:                    return str;
247:            }
248:
249:            public void setString(SFieldMeta field, String value) {
250:                setObject(field, value);
251:            }
252:
253:            /** Casts getObject() to int iff a Number, see getString().
254:             Returns 0 if null, following JDBC. */
255:            public int getInt(SFieldMeta field) {
256:                Object val = getObject(field);
257:                return SJSharp.object2Int(val);
258:            }
259:
260:            public void setInt(SFieldMeta field, int value) {
261:                setObject(field, SJSharp.newInteger(value));
262:            }
263:
264:            /** Casts getObject() to long iff a Number, see getString().
265:             Returns 0 if null, following JDBC.  Note that longs may not be
266:             accurately supported by the database -- see SFieldLong.*/
267:            public long getLong(SFieldMeta field) {
268:                Object val = getObject(field);
269:                return SJSharp.object2Long(val);
270:            }
271:
272:            public void setLong(SFieldMeta field, long value) {
273:                setObject(field, SJSharp.newLong(value));
274:            }
275:
276:            /** Casts getObject() to double iff a Number, see getString().
277:             Returns 0 if null, following JDBC. */
278:            public double getDouble(SFieldMeta field) {
279:                Object val = getObject(field);
280:                return SJSharp.object2Double(val);
281:            }
282:
283:            public void setDouble(SFieldMeta field, double value) {
284:                setObject(field, SJSharp.newDouble(value));
285:            }
286:
287:            /** Casts getObject() to double iff a Number, see getString().
288:             Returns false if null. */
289:            public boolean getBoolean(SFieldMeta field) {
290:                Object val = getObject(field);
291:                return val == Boolean.TRUE;
292:            }
293:
294:            public void setBoolean(SFieldMeta field, boolean value) {
295:                setObject(field, value ? Boolean.TRUE : Boolean.FALSE);
296:            }
297:
298:            /** etc. for Timestamp. */
299:            public java.sql.Timestamp getTimestamp(SFieldMeta field) {
300:                java.sql.Timestamp val = ((java.sql.Timestamp) getObject(field));
301:                return val;
302:            }
303:
304:            /** Note that value should normally be a java.sql.Timestamp (a
305:             subclass of java.util.Date).  However, if it is a java.util.Date
306:             instead, then it will replaced by a new java.sql.Timestamp
307:             object before being set.  This is convenient for people using
308:             java.util.Date as their main date type.  */
309:            public void setTimestamp(SFieldMeta field, java.util.Date value) {
310:                setObject(field, value);
311:            }
312:
313:            /** etc. for Date. */
314:            public java.sql.Date getDate(SFieldMeta field) {
315:                java.sql.Date val = ((java.sql.Date) getObject(field));
316:                return val;
317:            }
318:
319:            /** See {@link #setTimestamp} for discussion of Date parameter.*/
320:            public void setDate(SFieldMeta field, java.util.Date value) {
321:                setObject(field, value);
322:            }
323:
324:            /** etc. for Time. */
325:            public java.sql.Time getTime(SFieldMeta field) {
326:                java.sql.Time val = ((java.sql.Time) getObject(field));
327:                return val;
328:            }
329:
330:            /** See {@link #setTimestamp} for discussion of Date parameter.*/
331:            public void setTime(SFieldMeta field, java.util.Date value) {
332:                setObject(field, value);
333:            }
334:
335:            /** etc. for BigDecimal. */
336:            public BigDecimal getBigDecimal(SFieldMeta field) {
337:                BigDecimal val = ((BigDecimal) getObject(field));
338:                return val;
339:            }
340:
341:            public void setBigDecimal(SFieldMeta field, BigDecimal value) {
342:                setObject(field, value);
343:            }
344:
345:            /** Casts getObject() to byte[]. */
346:            public byte[] getBytes(SFieldMeta field) {
347:                Object val = getObject(field);
348:                return (byte[]) val;
349:            }
350:
351:            public void setBytes(SFieldMeta field, byte[] value) {
352:                setObject(field, value);
353:            }
354:
355:            /** Gets a record referenced by <code>this.field</code>.  does a
356:             lazy lookup if necessary. */
357:            public SRecordInstance getReference(SFieldReference field,
358:                    long sqy_flags) {
359:                // sqy_bitSet etc. needs to be passed through.
360:                return (SRecordInstance) getObject(field, sqy_flags);
361:            }
362:
363:            public SRecordInstance getReference(SFieldReference field) {
364:                return getReference(field, 0);
365:            }
366:
367:            public void setReference(SFieldReference field,
368:                    SRecordInstance value) {
369:                setObject(field, value);
370:            }
371:
372:            /**
373:             Same as getReference, except that if the referenced record is not
374:             already in the cache then it will simply return Boolean.FALSE.
375:             Particularily useful for detached records where it is not possible
376:             to do such a query.<p>
377:             
378:             If the underlying scalar key values are null, then this always
379:             null because there is no record to retrieve.  It is only for a
380:             non-null, non-retrieved references that this returns FALSE.
381:             */
382:            public Object getReferenceNoQuery(SFieldReference field) {
383:                return getObject(field, SCon.SQY_REFERENCE_NO_QUERY);
384:            }
385:
386:            /** Sets this record to be dirty so that it will be updated in the
387:             database. Normally done implicitly by setting a specific column.
388:             But may occasionally be useful after a findOrInsert() to add a
389:             record that contains nothing appart from its primary key. */
390:            public void setDirty() {
391:                if (readOnly)
392:                    throw new SException.Error("Record retrieved read only "
393:                            + this );
394:                dirty = true;
395:                if (sConnection != null && updateListIndex == -1) {
396:                    updateListIndex = sConnection.updateList.size();
397:                    sConnection.updateList.add(this );
398:                }
399:            }
400:
401:            /** True iff this record is dirty but not yet flushed to the
402:             database.  May be both dirty and unattached.<p>
403:             
404:             A record is not dirty if a record has been flushed to the
405:             database but the transaction not committed.<p>
406:             
407:             ## Should add <tt>wasEverDirty</tt> method for both record and
408:             fields for validation tests. */
409:            public boolean isDirty() {
410:                return dirty;
411:            }
412:
413:            /** Tests whether just field is dirty. */
414:            public boolean isDirty(SFieldMeta field) {
415:                boolean dirty = (bitSets[field.fieldIndex] & SCon.INS_DIRTY) != 0;
416:                return dirty;
417:            }
418:
419:            /** Was in the cache before the most recent findOrCreate.  
420:             * (Will always been in the cache after a findOrCreate.)
421:             * Used to prevent two create()s for the same key.
422:             * Also for unit tests.
423:             */
424:            public boolean wasInCache() {
425:                return wasInCache;
426:            }
427:
428:            /** Sets a flag to delete this record when the transaction is
429:             commited.  The record is not removed from the
430:             transaction cache.  Any future <code>findOrCreate</code> will
431:             thus return the deleted record but its fields may not be
432:             referenced.  (<code>isDeleted</code> can be used to determine
433:             that the record has been deleted.)<p>
434:             
435:             The record is only deleted from the database when the
436:             transaction is committed or is flushed.  Thus a transaction that
437:             nulls out references to a parent record and then deletes the
438:             parent record will not cause a referential integrity violation
439:             because the update order is preserved so that the updates will
440:             (normally) be performed before the deletes.<p>
441:             
442:             Note that for Optimistic locks only those fields that were
443:             retrieved are checked for locks.  */
444:            public void deleteRecord() {
445:                if (fieldValues == null || deleted)
446:                    throw new SException.Error(
447:                            "Cannot delete destroyed record " + this );
448:                setDirty();
449:                deleted = true;
450:            }
451:
452:            public boolean isDeleted() {
453:                return deleted;
454:            }
455:
456:            /** Often called after {SRecordMeta#findOrCreate} to determine
457:             whether a row was retrieved from the database (not a new row) or
458:             whether this record represents a new row that will be inserted
459:             when it is flushed.*/
460:            public boolean isNewRow() {
461:                return newRow;
462:            }
463:
464:            /** Throws an excpetion if !{@link #isNewRow}.  Handy, use often in
465:             your code to trap nasty errors. */
466:            public void assertNewRow() {
467:                if (!isNewRow())
468:                    throw new SException.Error("Not a new row " + this );
469:            }
470:
471:            /** @see #assertNewRow */
472:            public void assertNotNewRow() {
473:                if (isNewRow())
474:                    throw new SException.Error("Is a new row " + this );
475:            }
476:
477:            /** True if the record has valid data, ie. it has not been
478:             destroyed.  (This has nothing to do with validateRecord.)*/
479:            public boolean isValid() {
480:                return fieldValues != null;
481:            }
482:
483:            /** True if the field has been queried as part of the current
484:             transaction and so a get is valid.  Use this to guard
485:             validations if partial column queries are performed.  See
486:             isDirty.*/
487:            public boolean isValid(SFieldMeta field) {
488:                return (bitSets[field.fieldIndex] & SCon.INS_VALID) != 0;
489:            }
490:
491:            /*
492:             * Fuzzy Semantics for Nulls.
493:             * @deprecated use getReferenceNoQuery
494:             
495:             public boolean isReferenceAvailable( SFieldReference reference )
496:             {
497:             SRecordInstance result
498:             = (SRecordInstance)fieldValues[reference.fieldIndex];
499:             
500:             return result != null && result.isValid();
501:             }
502:             */
503:
504:            /** Flush this instance to the database.  Normally called by
505:             <code>SConnection.flush()</code> in reponse to
506:             <code>commit</code> but can also be called expicitly if the
507:             update order needs to be modified.  This method does nothing
508:             unless the record is dirty.<p>
509:             
510:             ## This should really utilize the new batching techniques if the
511:             JDBC driver supports them.  This would substantially minimize
512:             the nr of round trips to the server.<p>
513:             
514:             @see SConnection#flush
515:             */
516:            public void flush() {
517:                SRecordUpdater.flush(this );
518:            }
519:
520:            /** Returns the list of fields that are used as part of the
521:             optimistic locking.  Includes Primary key fields, any valid
522:             fields that are dirty, but not references.*/
523:            SArrayList keyFieldMetas(boolean optimistic, Object[] keyMetaValues) {
524:                SRecordMeta meta = getMeta();
525:                SArrayList res = new SArrayList(meta.sFieldMetas.size());
526:                for (int fx = 0, addCounter = 0; fx < meta.sFieldMetas.size(); fx++) {
527:                    SFieldMeta fld = (SFieldMeta) meta.sFieldMetas.get(fx);
528:                    if (optimistic) {
529:                        if ((bitSets[fld.fieldIndex] & SCon.INS_VALID) != 0
530:                                && ((bitSets[fld.fieldIndex] & SCon.INS_DIRTY) != 0
531:                                        || fld.isPrimaryKey || deleted)
532:                                && !(fld instanceof  SFieldReference))
533:                            if (!fld.getBoolean(SCon.SOPTIMISTIC_UNCHECKED)) {
534:                                res.add(fld);
535:                                keyMetaValues[addCounter++] = optimisticFieldValues[fld.fieldIndex];
536:                            }
537:                    } else { // pesimistic
538:                        if (fld.isPrimaryKey
539:                                && !(fld instanceof  SFieldReference)) {
540:                            res.add(fld);
541:                            keyMetaValues[addCounter++] = fieldValues[fld.fieldIndex];
542:                        }
543:                    }
544:                }
545:                return res;
546:            }
547:
548:            /** Destroys this instance so that it can no longer be used.  Also
549:             nulls out variables so to reduce risk of memory leaks.  Note
550:             that it does not remove the record from the transaction cache
551:             and update list -- it cannot be called on its
552:             own.*/
553:            void incompleteDestroy() {
554:                //SLog.slog.temp("Destroying " + ri);
555:                fieldValues = null;
556:                bitSets = null;
557:                updateListIndex = -2;
558:                sConnection = null;
559:            }
560:
561:            /** Flushes this record instance to the database, removes it from
562:             the transaction cache, and then destroys the record so that it
563:             can no longer be used.  Any future <code>findOrCreate</code>
564:             will requery the data base and produce a new record.<p>
565:             
566:             This is useful if one wants to do raw JDBC updates on the
567:             record, and be guaranteed not to have an inconsistent cache.
568:             (Where bulk updates can be used they are several times faster
569:             than updates made via the JVM -- see the benchmarks section in
570:             the white paper.)<p>
571:             
572:             Problems with the update order can generally be avoided by first
573:             flushing the entire connection.<p>
574:             
575:             @see SRecordMeta#flushAndPurge
576:             @see SConnection#flushAndPurge
577:             @see SConnection#flush
578:             */
579:            public void flushAndPurge() {
580:                flush();
581:                dirtyPurge();
582:            }
583:
584:            /**
585:             Removes this record from the cache but without flushing it.  Any
586:             changes to it will be lost.  This might be useful if the record is
587:             being manually updated/deleted and you want to deliberately ignore
588:             any direct changes.<p>
589:             
590:             Dangerous, use with care.<p>
591:             
592:             @see #flushAndPurge
593:             */
594:            public void dirtyPurge() {
595:                SConnection scon = SConnection.getBegunConnection();
596:                scon.transactionCache.remove(this );
597:                incompleteDestroy();
598:            }
599:
600:            /** True if this instance is attached to the current begun
601:             transaction.  Exception if is attached but not to the current
602:             transaction or the current transaction has not begun. */
603:            public boolean isAttached() {
604:                if (sConnection == null)
605:                    return false;
606:                SConnection scon = SConnection.getBegunConnection();
607:                if (sConnection != scon)
608:                    throw new SException.Error("Instance " + toStringDefault()
609:                            + " attached to " + sConnection
610:                            + " but current connection is " + scon);
611:                return true;
612:            }
613:
614:            /** Set this record to be locked Optimistically.  Assert that it is
615:             not dirty and copy the current value of all relevant fields.
616:             These can be compared with values stored in the database at
617:             flush time.<p>
618:             
619:             The copy is cheap because it is only copying pointers.  However,
620:             if the optimisitic lock fails then an exception will be thrown.
621:             Mainly used for long lived transactions. <p>
622:             */
623:            void setOptimistic(boolean redo) {
624:                if (readOnly)
625:                    return;
626:                if (optimisticFieldValues == null) {
627:                    // Already done, maybe findOrCreating the same record twice
628:                    if (isDirty())
629:                        throw new SException.Error(
630:                                "Cannot make dirty record optimisitic " + this );
631:                    optimisticFieldValues = new Object[fieldValues.length];
632:                    redo = true;
633:                }
634:                if (redo)
635:                    for (int ox = 0; ox < fieldValues.length; ox++) {
636:                        if ((bitSets[ox] & SCon.INS_VALID) != 0)
637:                            optimisticFieldValues[ox] = fieldValues[ox];
638:                    }
639:            }
640:
641:            /** Detach this instance from the current transactions.  Sets
642:             optimistic locking if the record was updatable. ,p>
643:             
644:             If a future query in the same transaction retrieves a record
645:             with the same key then it will be a different instance.
646:             Ie. this really does detach the record from the transaction.<p>
647:             
648:             Nulls any references to records that have not already been
649:             detached so that they will not be serialized.  The keys are kept
650:             so that the references can be reconstructed later.  (This does
651:             NOT recursively detach any referenced records -- you have to
652:             explicitly detach all the records that you need.)<p>
653:             
654:             Set <code>nullRefs<code> to false to break instance indirect circular
655:             references, eg. if an Employee e  manages someone that manages e.  Very
656:             rarely needed.  It is OK to detach a record twice.*/
657:            public void detach(boolean nullRefs) {
658:                setOptimistic(false);
659:                if (isDirty())
660:                    throw new SException.InternalError(
661:                            "Dirty but not updatable." + this );
662:                if (isAttached())
663:                    sConnection.transactionCache.remove(this );
664:                sConnection = null;
665:
666:                // Null any non detached references.
667:                if (nullRefs) {
668:                    SArrayList fields = getMeta().sFieldMetas;
669:                    for (int fx = 0; fx < fields.size(); fx++) {
670:                        SFieldMeta field = (SFieldMeta) fields.get(fx);
671:                        if (field != null && field instanceof  SFieldReference) {
672:                            SRecordInstance refed = (SRecordInstance) fieldValues[field.fieldIndex];
673:                            if (refed != null && refed.isAttached())
674:                                fieldValues[field.fieldIndex] = null;
675:                        }
676:                    }
677:                }
678:            }
679:
680:            /** detach(true) */
681:            public void detach() {
682:                detach(true);
683:            }
684:
685:            /** Attach a detached record to the current transaction.  A record
686:             with the same key must not already exist in this transaction,
687:             although this restriction may be relaxed later.  Recursively
688:             attaches any referenced records.  <p>
689:             
690:             Currently, the returned value is just <tt>this</tt>.  However, a
691:             future version may allow the unattached record to be merged with
692:             an existing record, so one should always assume that the record
693:             may change identity during attachment.<p>
694:             
695:             Attaching the same record twice is OK.<p>
696:             
697:             If the record has a generated primary key then the key is generated 
698:             as part of the attachement processing.<p>
699:             
700:             */
701:            public SRecordInstance attach() {
702:                /// Check not already attached
703:                if (isAttached())
704:                    return this ;
705:
706:                SConnection scon = SConnection.getBegunConnection();
707:
708:                // If this record has a generated key which has not yet been set, it must have
709:                // been creatd with SRecordMeta.createDetached(). We must generate a key for
710:                // it here
711:                SRecordMeta meta = getMeta();
712:                SFieldMeta keyField = (SFieldMeta) meta.keySFieldMetas.get(0);
713:                SGenerator gen = (SGenerator) keyField
714:                        .getProperty(SCon.SGENERATED_KEY);
715:                if (gen != null && // has generated key
716:                        (!isValid(keyField) || getObject(keyField) == null)) // it hasn't been set
717:                {
718:                    gen.updateWithGeneratedKey(this , keyField);
719:
720:                    //long generatedId = scon.sDriver.generateKey(meta, keyField);
721:                    //setLong( keyField, generatedId );
722:                } else // Key has been (or should have been) set
723:                {
724:                    // Ensure that all primary key fields have been set
725:                    for (int kx = 0; kx < meta.keySFieldMetas.size(); kx++) {
726:                        keyField = (SFieldMeta) meta.keySFieldMetas.get(kx);
727:                        if (!(keyField instanceof  SFieldReference)) {
728:                            if (!isValid(keyField)
729:                                    || getObject(keyField) == null) {
730:                                throw new SException.Error(
731:                                        "Attempting to attach a record with null key field "
732:                                                + keyField
733:                                                + ". This is most likely because you created a record using "
734:                                                + "createDeatched(), and forgot to set one of its primary keys, "
735:                                                + "or have set it to null.");
736:                            }
737:                        }
738:                    }
739:
740:                    /// Check no other record in transaction with the same key
741:                    SRecordInstance existing = (SRecordInstance) scon.transactionCache
742:                            .get(this );
743:                    if (existing != null && existing != this )
744:                        throw new SException.Error("Cannot attach " + this 
745:                                + " because there is already a record "
746:                                + existing);
747:                }
748:
749:                /// Attach this record
750:                sConnection = scon;
751:                scon.transactionCache.put(this , this );
752:                if (isDirty())
753:                    setDirty(); // ie. add to sConnection.updateList.
754:
755:                /// Recursively attach any referenced records.
756:                SArrayList fields = meta.sFieldMetas;
757:                for (int fx = 0; fx < fields.size(); fx++) {
758:                    SFieldMeta field = (SFieldMeta) fields.get(fx);
759:                    if (field instanceof  SFieldReference) {
760:                        SRecordInstance refed = (SRecordInstance) fieldValues[field.fieldIndex];
761:                        if (refed != null && refed.isValid())
762:                            // ## isValid test only because we incorrectly serialize them.
763:                            refed.attach();
764:                    }
765:                }
766:
767:                return this ; // May change later.
768:            }
769:
770:            /** toString just shows the Key field(s).  It is meant to be
771:             consise, often used as part of longer messages.*/
772:            public String toString() {
773:                return toStringDefault();
774:            }
775:
776:            /**
777:             * Default behavior of toString(). This was split out from the toString() method
778:             * to avoid infinite recursion if a subclass of SRecordInstance overrode toString()
779:             * to use any of the get...() methods.
780:             */
781:            String toStringDefault() {
782:                StringBuffer ret = new StringBuffer("["
783:                        + SUte.cleanClass(getClass()) + " ");
784:                if (fieldValues == null)
785:                    ret.append("[Destroyed SRecordInstance]");
786:                else {
787:                    boolean first = true;
788:                    for (int kx = 0; kx < getMeta().keySFieldMetas.size(); kx++) {
789:                        SFieldMeta fld = (SFieldMeta) getMeta().keySFieldMetas
790:                                .get(kx);
791:                        if (!(fld instanceof  SFieldReference)) {
792:                            //.sFieldReference == null) { // Not foreign key.
793:                            if (!first)
794:                                ret.append(", ");
795:                            first = false;
796:                            int pkx = fld.fieldIndex;
797:                            //ret.append(fld);
798:                            ret.append(fieldValues[pkx]);
799:                        }
800:                    }
801:                }
802:                if (newRow)
803:                    ret.append(" NewRecord");
804:                if (deleted)
805:                    ret.append(" Deleted");
806:                ret.append("]");
807:                return ret.toString();
808:            }
809:
810:            /** For debugging like toString(), but shows all the fields. */
811:            public String allFields() {
812:                StringBuffer ret = new StringBuffer("["
813:                        + SUte.cleanClass(getClass()) + " ");
814:                if (fieldValues == null)
815:                    ret.append("[Destroyed SRecordInstance]");
816:                else {
817:                    SArrayList fields = getMeta().sFieldMetas;
818:                    for (int px = 0; px < fields.size(); px++) {
819:                        if (px > 0)
820:                            ret.append("| ");
821:                        int idx = ((SFieldMeta) fields.get(px)).fieldIndex;
822:                        ret.append(fieldValues[idx]);
823:                    }
824:                }
825:                if (newRow)
826:                    ret.append(" NewRecord");
827:                if (deleted)
828:                    ret.append(" Deleted");
829:                ret.append("]");
830:                return ret.toString();
831:            }
832:
833:            /** The main field validation method, this is specialized for
834:             records that need to perform field level validation.  
835:             It is called each time a field is set a value, (now) including keys.<p>
836:             
837:             Throw an SValidationException if not OK.  The value is not assigned, and
838:             the transaction can continue.<p>
839:             
840:             This is called after the value has been converted to its proper
841:             type, eg. from a String to a Double.  (Which is why it is not a
842:             good place to also do conversions.)<p>
843:             
844:             This is called for key values as well. 
845:             This is only for newly created records but is during the findOrCreate 
846:             -- ie it is called even if the record is never made dirty and thus saved.   
847:
848:             See ADemo and ValidationTest for examples.  */
849:            public void validateField(SFieldMeta field, Object newValue) {
850:            }
851:
852:            /** The main record validation method,
853:             this is specialized for records that need to perform validation.
854:             Throw an SValidationException if not OK.<p>
855:             
856:             This is called just before a record would be flushed.  Only
857:             dirty records are validated.  If the validation fails then the
858:             record is not flushed.  (It may also be called directly by the
859:             application to validate the record before it is flushed.)<p>
860:             
861:             Use this routine when a record may be in a temporarily invalid
862:             state, but which must be corrected before flushing.  This is
863:             common when there is a more complex relationship between
864:             different fields that cannot be validated until all the fields
865:             have been assigned values.<p>
866:             
867:             If an exception is thrown then the condition will need to be
868:             corrected or the transaction will need to be rolled back.<p>
869:             
870:             See ADemo for an example.  */
871:            public void validateRecord() {
872:            }
873:
874:            /**
875:             * This method is called if the record is detached, and a request is made for
876:             * a reference that has not also been detached. This method gives you one last
877:             * chance to return the referenced record before an exception is thrown.
878:             * <p>
879:             * By default, this method just returns null to indicate that no last-ditch effort
880:             * is made to fetch missing references, so you must override it to do
881:             * something more useful (i.e. fetch the reference from the database)
882:             * <p>
883:             * Warning: Note that because this record and the fetched reference are obtained
884:             * in separate transactions, any consistency constraints between the two objects
885:             * might not hold.
886:             *
887:             * @param reference The reference that is being followed
888:             * @param keys Object[] keys for the reference, just as you would pass them
889:             *  to THE REFERENCED record's findOrCreate()
890:             * @return SRecordInstance Return the fetched record, or null, if no attempt was
891:             *  made. NOTE: currently, this does not cover the awkward case where an attempt IS
892:             *  made, but the value returned is NULL, anyway.
893:             */
894:            protected SRecordInstance getReferenceWhileDetached(
895:                    SFieldReference reference, Object[] keys) {
896:                return null;
897:            }
898:
899:            /**
900:             * Null direct references from this record instance to other
901:             * instances.  Leaves the actual scalar referencing key fields
902:             * alone.  May only be called on a detached object.  This method was
903:             * created for two reasons:
904:             *
905:             * <ol>
906:             * <li>Reduce communication burden when sending detached records from a client back
907:             *  to the server for processing
908:             * <li>Avoid problems when recursively attaching referenced records that we really did not want.
909:             *  attach() currently does not allow multiple attach'es of same record. Even though this
910:             *  reason may evenutually go away, the first reason will remain.
911:             * </ol>
912:             *
913:             * @deprecated Detach has always nulled refferences, and attach allows a record to be attached multiple times.
914:             */
915:            public void nullReferences() {
916:                SArrayList fields = getMeta().sFieldMetas;
917:                for (int fx = 0; fx < fields.size(); fx++) {
918:                    SFieldMeta field = (SFieldMeta) fields.get(fx);
919:                    if (field instanceof  SFieldReference) {
920:                        fieldValues[field.fieldIndex] = null;
921:                        if (optimisticFieldValues != null) {
922:                            optimisticFieldValues[field.fieldIndex] = null;
923:                        }
924:                    }
925:                }
926:            }
927:
928:            /**
929:             * Override Object.clone() to make copies of the internal fieldValues array.
930:             * <p>This method may only be used while the object is detached.
931:             * 
932:             * @deprecated Probably not useful any more, and a little dangerous.
933:             */
934:            public Object clone() {
935:                if (isAttached())
936:                    throw new SException.Error("Can't clone " + this 
937:                            + " while attached");
938:
939:                try {
940:                    SRecordInstance clone = (SRecordInstance) super .clone();
941:                    if (fieldValues != null) {
942:                        clone.fieldValues = new Object[fieldValues.length];
943:                        System.arraycopy(fieldValues, 0, clone.fieldValues, 0,
944:                                fieldValues.length);
945:                    }
946:                    return clone;
947:                } catch (CloneNotSupportedException ex) {
948:                    throw new SException.InternalError(
949:                            "Should never happen, since this class implements Cloneable");
950:                }
951:            }
952:
953:            /** Exception thrown due to broken optimistic locks.  This one may
954:             be worth trapping by the application so gets its own class. */
955:            public static class BrokenOptimisticLockException extends
956:                    SException {
957:                SRecordInstance instance;
958:
959:                public BrokenOptimisticLockException(SRecordInstance instance) {
960:                    super ("Broken Optimistic Lock " + instance, null);
961:                    this .instance = instance;
962:                }
963:
964:                /** The record that had the broken optimistic lock. */
965:                public SRecordInstance getRecordInstance() {
966:                    return instance;
967:                }
968:            }
969:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.