001: package simpleorm.core;
002:
003: import simpleorm.properties.*;
004:
005: import java.util.HashMap;
006: import java.sql.ResultSet;
007: import java.io.*;
008: import java.sql.*;
009:
010: /*
011: * Copyright (c) 2002 Southern Cross Software Queensland (SCSQ). All rights
012: * reserved. See COPYRIGHT.txt included in this distribution.
013: */
014:
015: /** Each instance defines the meta data for a field in an {@link
016: SRecordMeta}. Subclasses are used for specific data types, with
017: {@link SFieldObject} being the most generic. Like JDBC, type
018: conversions are made automatically.<p>
019:
020: Internally, the types are stored accurately. Ie. the
021: <code>SRecordInstance.fieldValues</code> objects are the exact
022: types as declared (String, Integer, Employee etc.) However,
023: generous automatic conversions are performed both when accessing
024: these from the application and when getting and setting columns in
025: the database.<p>
026:
027: See SSimpleORMProperties for a descripiton of how properties are
028: used with fields.<p>*/
029:
030: public abstract class SFieldMeta extends SPropertyMap implements
031: Serializable {
032: /** The record that this field belongs to. */
033: SRecordMeta sRecordMeta;
034:
035: /** The indext into SRecordMeta.fields. */
036: int fieldIndex;
037:
038: /** Is part of a primary key, may also be a foreign key. ### Propertyize.*/
039: transient boolean isPrimaryKey = false;
040:
041: /** Can only be read, not set. Mainly SCOLUMN_NAME */
042: transient boolean isReadOnly = false;
043:
044: /** Not null means this field is a foreign key member of the
045: SFieldReference. Overlapping foreign keys are not supported so
046: this is a scalar. (The semantics of setField are vague
047: Overlapping keys.) Strict inverse of
048: <code>SFieldReference.foreignKeyFields</code>.*/
049: transient SFieldReference sFieldReference = null;
050:
051: /** If sFieldRefrence != null, then this refers to the corresponding
052: key field in the referenced table. */
053: transient SFieldMeta referencedKeyField = null;
054:
055: /** The last key value generated for this
056: <code>SGENERATED_KEY</code> field in this JVM. Used by the
057: default driver as a hack to minimize collisions with the SELECT
058: MAX method. Synchronize all access.*/
059: transient private long lastGeneratedKeyValue = 0;
060:
061: /** Creates a new field for <code>sRecord</code> corresponding to
062: <code>columnName</code>. <code>pvalues</code> are an arbitrary
063: list of properties that can be associated with this
064: field. <code>fieldName</code> is only used for the fieldMap and
065: defaults to the <code>columnName</code> or prefix for
066: references. */
067: SFieldMeta(SRecordMeta sRecord, String columnName,
068: String fieldName, SPropertyValue[] pvalues) {
069: //SLog.slog.debug(" SFieldMeta " + sRecord + columnName);
070: this .sRecordMeta = sRecord;
071:
072: setPropertyValues(pvalues);
073: String colName = (String) putDefaultProperty(SCon.SCOLUMN_NAME,
074: columnName);
075: String fldName = (String) putDefaultProperty(SCon.SFIELD_NAME,
076: fieldName);
077:
078: /// Check no duplicate column names, can happen for references.
079: for (int mx = 0; mx < sRecord.sFieldMetas.size(); mx++) {
080: SFieldMeta fm = (SFieldMeta) sRecord.sFieldMetas.get(mx);
081: String xcol = fm.getString(SCon.SCOLUMN_NAME);
082: if (xcol != null && xcol.equals(colName))
083: throw new SException.Error("Duplicate Column Name "
084: + colName);
085: }
086:
087: /// Set up new record.
088: sRecordMeta.sFieldMetas.add(this );
089: fieldIndex = sRecordMeta.sFieldMetas.size() - 1;
090: sRecordMeta.sFieldNames.add(fldName);
091: sRecordMeta.fieldMap.put(fldName, this );
092: if (getBoolean(SCon.SPRIMARY_KEY)) {
093: isPrimaryKey = true;
094: sRecordMeta.keySFieldMetas.add(this );
095: }
096: if (getProperty(SCon.SCOLUMN_QUERY) != null)
097: isReadOnly = true;
098: if (sRecordMeta.keySFieldMetas.size() == 0)
099: throw new SException.Error("No Primary Key declared for "
100: + sRecordMeta + " (Must be first fields)");
101: // Important for Employee.Manager refs Employee
102: }
103:
104: /** Common case for all fields but References. */
105: SFieldMeta(SRecordMeta sRecord, String columnName,
106: SPropertyValue[] pvalues) {
107: this (sRecord, columnName, columnName, pvalues);
108: }
109:
110: /** Clone this key field to be a foreign key to <code>rmeta</code>
111: of the same type.*/
112: abstract SFieldMeta makeForeignKey(SRecordMeta rmeta,
113: String prefix, SPropertyValue[] pvals);
114:
115: /** Return default SDATA_TYPE property. */
116: abstract String defaultDataType();
117:
118: /** Dispatched from SRecordInstance.getObject(), this is specialized
119: for references etc. */
120: Object getFieldValue(SRecordInstance instance, long sqy_flags) {
121: if (!instance.isValid())
122: throw new SException.Error(
123: "Cannot access destroyed record " + instance);
124: if (instance.deleted)
125: throw new SException.Error(
126: "Attempt to access Deleted record " + instance);
127: if (sRecordMeta != instance.getMeta()) // ie. identical
128: throw new SException.Error("Field " + this + " is not in "
129: + instance.getMeta());
130: // Maybe trigger lazy read of object later.
131:
132: if ((instance.bitSets[fieldIndex] & SCon.INS_VALID) == 0
133: && !(this instanceof SFieldReference))
134: throw new SException.Error("Cannot access unqueried field "
135: + this + " in " + instance);
136: Object res = getRawFieldValue(instance, sqy_flags);
137: if (SLog.slog.enableFields())
138: SLog.slog.fields("Got " + instance.toStringDefault() + "."
139: + this + " == " + res);
140: return res;
141: }
142:
143: /** Specialized in SFieldReference */
144: Object getRawFieldValue(SRecordInstance instance, long sqy_flags) {
145: return instance.fieldValues[fieldIndex];
146: }
147:
148: /** Does generic checks before calling field type specific handler.
149: See SRecordInstance.setObject for a description fo the full
150: flow.<p>
151:
152: The internal flow is:-
153: <xmp>
154: SRecordInstance.setString(field, value)
155: SRecordInstance.setObject(field, value)
156: SFieldMeta.setFieldValue(this)
157: - Generic validation incl. Not primary key.
158: - Check if changed.
159: SField*.convertToField(value)
160: SFieldMeta|Reference.setRawFieldValue(instance, value)
161: </xmp>
162:
163: * Note that setRawFieldValue is also called directly for keys etc.
164: *
165: **/
166:
167: void setFieldValue(SRecordInstance instance, Object rawValue) {
168:
169: // ### INS_VALID & UPDATABLE tests are missing?
170:
171: /// Check instance valid for setting this field.
172: if (instance.fieldValues == null)
173: throw new SException.Error(
174: "Cannot access destroyed record " + instance);
175: if (instance.deleted)
176: throw new SException.Error("Cannot access deleted record "
177: + instance);
178: if (sRecordMeta != instance.getMeta()) // ie. identical
179: throw new SException.Error("Field " + this + " is not in "
180: + instance.getMeta());
181: if (isPrimaryKey && instance.isAttached()) // See SRecordMeta createDetached.
182: throw new SException.Error(
183: "Cannot change primary key field " + this + " in "
184: + instance);
185: if (sFieldReference != null)
186: throw new SException.Error(
187: "Cannot directly update foreign key " + this
188: + " in " + instance + ". Update "
189: + sFieldReference + " instead.");
190: // If users could directly update the foreign keys they could
191: // make the relationship between foreign keys and the references
192: // inconsistent. When findOrCreate becomes truely lazy, the
193: // overheads in creating the referenced object will be
194: // negligible. However, this may change if overlapping foreign
195: // keys are implemented.
196:
197: checkUpdatable(instance);
198:
199: /// Convert rawValue if necessary.
200: Object convValue = null;
201: try {
202: convValue = convertToField(rawValue);
203: } catch (Exception ex) {
204: throw new SException.Data("Converting " + rawValue
205: + " for " + instance + "." + this , ex);
206: }
207:
208: /// Now do any user validations.
209: instance.validateField(this , convValue);
210:
211: /// Set the field value if different.
212: Object fvalue = instance.fieldValues[fieldIndex];
213: if ((instance.bitSets[fieldIndex] & SCon.INS_VALID) == 0
214: || (convValue == null && fvalue != null)
215: || (convValue != null && (fvalue == null || !convValue
216: .equals(fvalue)))) {
217: rawSetFieldValue(instance, convValue);
218: instance.setDirty();
219: }
220: }
221:
222: /** Specialized for SReferences.*/
223: void checkUpdatable(SRecordInstance instance) {
224: if ((instance.bitSets[fieldIndex] & SCon.INS_READ_ONLY) != 0)
225: throw new SException.Error("Not queried for updating "
226: + this + " in " + instance);
227: }
228:
229: /** Specialized for References. Called from 1. setFieldValue(),
230: which sets the record dirty, 2. setPrimaryKey() for which we do
231: not want the record marked dirty,
232: 3. SFieldReference.rawSetFieldValue() (recursively) in which
233: case setFieldValue() should have already marked the record
234: dirty. */
235: void rawSetFieldValue(SRecordInstance instance, Object value) {
236: if (SLog.slog.enableFields())
237: SLog.slog.fields("Set " + instance + "." + this + " = "
238: + value);
239: if (isReadOnly)
240: throw new SException.Error(
241: "Attempt to set read only field " + instance + "."
242: + this + " to " + value);
243: instance.fieldValues[fieldIndex] = value;
244: instance.bitSets[fieldIndex] |= (byte) (SCon.INS_DIRTY | SCon.INS_VALID);
245: }
246:
247: /** Issues a JDBC get*() on the result set for the field and converts the database type
248: to the appropriate internal type, eg, Double for a double field. The first
249: column has sqlIndex==1. */
250: abstract Object queryFieldValue(ResultSet rs, int sqlIndex)
251: throws Exception;
252:
253: /** Converts the parameter from the users type to the correct internal Object type for this
254: field. Returns the object if no conversion necessary. Used by
255: <code>SRecordInstance.setObject</code> etc.*/
256: abstract Object convertToField(Object raw) throws Exception;
257:
258: /**
259: * Places a value in a prepared statement in the database representation
260: * used during SRecordInstance.flush.
261: * Can convert between internal values and database values, eg. TRUE to "Y".
262: *
263: * (This does NOT need to handle NULL values (those are handled seperately by SRecordInstance))
264: */
265: void writeFieldValue(PreparedStatement ps, int sqlIndex,
266: Object value) {
267: try {
268: ps.setObject(sqlIndex, writeFieldValue(value));
269: } catch (Exception ex) {
270: throw new SException.JDBC(ex);
271: }
272: }
273:
274: /**
275: * Converts a single value from internal representation to database representation.
276: * Used primarily by the writeFieldValue above, but also used for converting optimistic lock
277: * values in SRecordInstance.flush.<p>
278: *
279: * Overidden by SFieldBoolean. (not by SFieldString).
280: *
281: * NOTE: This does NOT need to handle NULL values (those are handled seperately by SRecordInstance)
282: */
283: Object writeFieldValue(Object value) {
284: return value;
285: }
286:
287: /** Lists the record and column name only. Useful in traces. */
288: public String toString() {
289: return "[F " + SUte.cleanClass(sRecordMeta.userClass) + "."
290: + getString(SCon.SCOLUMN_NAME) + "]";
291: }
292:
293: /** Lists all the details of the field. */
294: public String toLongerString() {
295: return "["
296: + this
297: + (isPrimaryKey ? " PKey" : "")
298: + (sFieldReference != null ? (" sFldRef "
299: + sFieldReference + " RefedKey " + referencedKeyField)
300: : "") + "]";
301: }
302:
303: /**
304: * Accessor to get the SRecordMeta of this field
305: */
306: public SRecordMeta getSRecordMeta() {
307: return sRecordMeta;
308: }
309:
310: /*
311: public Object validate(Object newValue) throws SValidationException{}
312: // Framework.
313: */
314:
315: /**
316: This is used to fudge version numbers in databases that do not
317: properly suport them. It is rough and fails if there are
318: multiple JVMs, or the user switches schemas in Oracle etc. (I
319: had used it as a backup of Oracle etc. but that was not a good
320: idea.)
321: */
322: synchronized long nextGeneratedValue(long minimum) {
323: if (lastGeneratedKeyValue < minimum)
324: lastGeneratedKeyValue = minimum;
325: else
326: ++lastGeneratedKeyValue;
327: return lastGeneratedKeyValue;
328: }
329:
330: /**
331: * Same purpose as corresponding method on SRecordMeta.
332: * <p>This method is implemented to return the pre-existing SFieldMeta object
333: */
334: protected Object readResolve() throws ObjectStreamException {
335: Object substituted = sRecordMeta.sFieldMetas.get(fieldIndex);
336: return substituted;
337: }
338: }
|