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: }
|