001: package simpleorm.core;
002:
003: import simpleorm.properties.*;
004:
005: import java.util.HashMap;
006: import java.sql.*;
007: import java.io.*;
008:
009: /*
010: * Copyright (c) 2002 Southern Cross Software Queensland (SCSQ). All rights
011: * reserved. See COPYRIGHT.txt included in this distribution.
012: */
013:
014: /** Defines the meta data for a {@link SRecordInstance} such as
015: the table name. Details about each field are stored in
016: {@link SFieldMeta} refered to from this object.<p>
017:
018: Thus Instance Variables of this class only describe the
019: definition of a Record, not instances or connections.<p>
020:
021: This class also contains routines to create new {@link SRecordInstance}es
022: such as {@link #findOrCreate} and {@link #select}.
023: (This packaging makes the calls shorter than using a static
024: method on SRecordInstance.)<p>
025:
026: */
027:
028: public class SRecordMeta extends SPropertyMap implements Serializable {
029: /** instances_ is a hash map of SRecordMetas. key is userClassName,
030: * and value is SRecordMeta. Static map of all SRecordMeta
031: * instances. used for de-serialization
032: */
033: private static HashMap instances_ = new HashMap();
034:
035: /** Name of underlying java class. Used for de-serialization */
036: private String userClassName;
037:
038: /** The underlying java class for this object. */
039: transient protected Class userClass;
040:
041: /** The SFieldMetas within this record. <p> This contains both the
042: foreign keys and then the sFieldReference, ie. it is totally
043: flattened.*/
044: transient SArrayList sFieldMetas = new SArrayList(20); // of SFieldMeta.
045:
046: /** The names of fields store redundantly.
047: Only to be used within the debugger.
048: */
049: transient public SArrayList sFieldNames = new SArrayList(20); // of String
050:
051: /** All the field names. use getField to look up the actual field.
052: See also SFIELD_MAP property etc.*/
053: public SArrayList getFieldNames() {
054: return sFieldNames;
055: }
056:
057: /** Exactly the same elements as SFieldMetas, but keyed by field
058: names which default to column names or reference table names or
059: prefixes. */
060: transient HashMap fieldMap = new HashMap(20);
061:
062: /** The Primary Key fields. <p> Contains both foreign keys
063: (recursively) and sFieldReference.*/
064: transient SArrayList keySFieldMetas = new SArrayList(2); // primary keys
065:
066: /** Create a new table/record definition.<p>
067:
068: @param userClass When a (@link #SRecordInstant} is created by
069: {@link #findOrCreate}, it is cast to this class which must extend
070: SRecordInstant. It is normally unique for each SRecordMeta, but
071: that is not strictly required.
072:
073: @param tableName The name of the SQL table that will be
074: associated with this record.
075:
076: @param pvalues Arbitrary properties that can be associated with
077: this record. Rarely used.
078: */
079: public SRecordMeta(Object userClass, String tableName,
080: SPropertyValue[] pvalues) {
081: Class clas = SJSharp.castToClass(userClass);
082: if (!SRecordInstance.class.isAssignableFrom(clas))
083: throw new SException.Error(
084: "Class must be an SRecordInstance " + clas);
085: this .userClass = clas;
086: putProperty(SCon.STABLE_NAME, tableName);
087:
088: if (pvalues != null)
089: setPropertyValues(pvalues);
090:
091: userClassName = clas.getName();
092: instances_.put(userClassName, this );
093: }
094:
095: public SRecordMeta(Object userClass, String tableName) {
096: this (userClass, tableName, null);
097: }
098:
099: /** Returns the field by SFIELD_NAME, or null if not found. */
100: public SFieldMeta getField(String fieldName) {
101: return (SFieldMeta) fieldMap.get(fieldName);
102: }
103:
104: /** Returns a <code>CREATE TABLE...</code> for this table. Delegates
105: to the database driver.*/
106: public String createTableSQL() {
107: return SConnection.getDriver().createTableSQL(this );
108: }
109:
110: /** Searches first the cache and then the database for a record with
111: the primary key == <code>keys</code>. If one is found returns
112: it, otherwise creates a new SRecordInstance prepopulated with
113: the primary key.<p>
114:
115: For multi valued concurrency databases such as Oracle and
116: PostgreSQL, the row is usually selected <code>FOR UPDATE</code> unless
117: {@link #SQY_READ_ONLY}.<p>
118:
119: In either case {@link #findOrCreate} never schedules the
120: record to be updated or inserted. That happens when one makes
121: it dirty by setting a field value or perhaps
122: SRecordInstance.setDirty().<P>
123:
124: The selectList is usually defaulted based on the sqy_bitSet.<p>
125:
126: If the record has identifying foreign keys the <code>keys</code>
127: must contain referenced records, not the actual foreign keys.<p>
128:
129: The APIs have been carefully designed so that SQL queries can be
130: made lazily. Ie. just schedule the query to be performed the
131: first time a field value is referenced. This enables efficient
132: batching techniques. However, this implementation performs the
133: queries eagerly, ie. the SQL query is issued by this method.<p>
134:
135: This always finds or creates a record within the context of a
136: connection. {@link #createDetached} can be used to create a new
137: record while not attached.
138:
139: See also the SQY_* constants.<p>
140:
141: @see #find
142: @see #create
143: */
144: public SRecordInstance findOrCreate(Object keys, long sqy_bitSet,
145: SFieldMeta[] selectList) {
146:
147: return SRecordFinder.findOrCreate(this , keys, sqy_bitSet,
148: selectList);
149: } // findOrCreate
150:
151: public SRecordInstance findOrCreate(Object keys) {
152: return findOrCreate(keys, 0);
153: }
154:
155: public SRecordInstance findOrCreate(Object keys, long sqy_bitSet) {
156: return findOrCreate(keys, sqy_bitSet, fieldList(sqy_bitSet
157: | SCon.SQY_NO_REFERENCES));
158: // Could also suppress Primary Keys from select list as we already
159: // have them. But keys are normally small so retrieving them is
160: // cheap and we get a good consistency check that they agree with
161: // the query.
162: }
163:
164: /**
165: * Same as findOrCreate(key) but returns null if not found
166: * instead of createing a new record.
167: * @see #mustFind(Object)
168: * @see #findOrCreate(Object)
169: */
170: public SRecordInstance find(Object keys) {
171: SRecordInstance found = findOrCreate(keys);
172: if (found.isNewRow())
173: found = null;
174: return found;
175: }
176:
177: /**
178: * Same as find(key) but throws an exception if the record is not found.
179: * @see #find
180: */
181: public SRecordInstance mustFind(Object keys) {
182: SRecordInstance found = findOrCreate(keys);
183: if (found.isNewRow())
184: throw new SException.Data("Record not found " + found);
185: return found;
186: }
187:
188: /**
189: * Same as findOrCreate(key) but asserts must be a new row.
190: * Also sets the row Dirty so that it will actually be inserted even if no
191: * other non-key fields are set.<p>
192: *
193: * It is assumed that the record is not in the database, you will
194: * get an SQL exception if it already exists. This saves a SELECT statement.
195: * But if there is any doubt use findOrCreate instead and then check .isNewRow.<p>
196: *
197: * A check is also made that the row is not already in the cache, ie. two calls to
198: * create with the same key will produce an exception.<p>
199: *
200: * @see #findOrCreate(Object, long, SFieldMeta[])
201: */
202: public SRecordInstance create(Object keys) {
203: SRecordInstance found = findOrCreate(keys,
204: SCon.SQY_ASSUME_CREATE);
205: found.assertNewRow(); // always true as
206: if (found.wasInCache())
207: throw new SException.Error(
208: "Attempt to create row that was already in the cache "
209: + found);
210: found.setDirty();
211: return found;
212: }
213:
214: /** Returns SFieldMetas to select given bitSet.
215: ### This needs a careful review. */
216: SFieldMeta[] fieldList(long sqy_bitSet) {
217: // ## This is a little expensive, results could be cached,
218: // along with the queries that are actually generated.
219: SFieldMeta[] result = null;
220: int resultSize = 0;
221: // Two passes, first to work out result size, second to build it up.
222: // This will be faster than another SArrayList.
223: for (int pass = 0; pass < 2; pass++) {
224: for (int fx = 0; fx < sFieldMetas.size(); fx++) {
225: SFieldMeta field = (SFieldMeta) sFieldMetas.get(fx);
226: //System.out.println("selectList " + pass + field + sqy_bitSet +
227: // SUte.inBitSet(sqy_bitSet, SQY_NO_FOREIGN_KEYS, SQY_) +
228: // SUte.inBitSet(sqy_bitSet, SQY_NO_REFERENCES, SQY_) );
229:
230: if (SUte.inBitSet(sqy_bitSet, SCon.SQY_PRIMARY_KEY,
231: SCon.SQY_)
232: && !field.isPrimaryKey)
233: continue; // ie.ONLY primary keys.
234: // ## Change to run down keySFieldMetas if primary. Or maybe
235: // just get rid of keySFieldMetas.
236:
237: // ## Problems if SDESCRIPTIVE etc. is a Reference
238: if (SUte.inBitSet(sqy_bitSet, SCon.SQY_DESCRIPTIVE,
239: SCon.SQY_)
240: && !field.getBoolean(SCon.SDESCRIPTIVE)
241: && !field.isPrimaryKey)
242: continue;
243: if (!SUte.inBitSet(sqy_bitSet, SCon.SQY_UNQUERIED,
244: SCon.SQY_)
245: && field.getBoolean(SCon.SUNQUERIED))
246: continue;
247: if (SUte.inBitSet(sqy_bitSet, SCon.SQY_NO_FOREIGN_KEYS,
248: SCon.SQY_)
249: && field.sFieldReference != null) // DataLoader
250: continue;
251: if (SUte.inBitSet(sqy_bitSet, SCon.SQY_NO_REFERENCES,
252: SCon.SQY_)
253: && field instanceof SFieldReference)
254: continue;
255:
256: if (SUte.inBitSet(sqy_bitSet,
257: SCon.SQY_NO_GENERATED_KEYS, SCon.SQY_)
258: && field.getProperty(SCon.SGENERATED_KEY) != null)
259: continue;
260:
261: // Not rejected so include this field.
262: if (pass == 1)
263: result[resultSize] = field;
264: resultSize++;
265: }
266: if (pass == 0)
267: result = new SFieldMeta[resultSize];
268: resultSize = 0;
269: }
270: return result;
271: }
272:
273: /** Given a list of fields, add primary key fields and then replace
274: any reference fields with their foreign keys. Normally one
275: works with references, but when queries are generated one needs
276: the ground columns. */
277: SFieldMeta[] expandSelectList(SFieldMeta[] fields) {
278: SArrayList xfields = new SArrayList(fields.length * 2);
279:
280: // Add primary keys if missing
281: mpxl: for (int mpx = 0; mpx < keySFieldMetas.size(); mpx++) {
282: SFieldMeta mp = (SFieldMeta) keySFieldMetas.get(mpx);
283: if (!(mp instanceof SFieldReference)) {
284: for (int qpx = 0; qpx < xfields.size(); qpx++)
285: if (xfields.get(qpx) == mp)
286: continue mpxl;
287: // Key not in query, add it. (Should be rare) Where are Java's array functions?
288: if (!xfields.contains(mp))
289: xfields.add(mp);
290: }
291: }
292:
293: // Expand References.
294: expandSelectListRecur(xfields, fields);
295: return (SFieldMeta[]) xfields.toArray(new SFieldMeta[0]);
296: }
297:
298: private void expandSelectListRecur(SArrayList xfields,
299: SFieldMeta[] fields) {
300: for (int fx = 0; fx < fields.length; fx++) {
301: SFieldMeta field = fields[fx];
302: if (field instanceof SFieldReference)
303: expandSelectListRecur(xfields,
304: ((SFieldReference) field).foreignKeyFields);
305: else if (!xfields.contains(field))
306: xfields.add(field);
307: }
308: }
309:
310: /** Produce an SQL Query, prepare it, and return an
311: {@link SPreparedStatement}. See that class for an example of its use.
312: <code>where</code> and <code>orderBy</code> are SFieldMetas that
313: arecopied literally into the query. The SQY_* BitSets are defined
314: in SConstants.<p>
315:
316: (This delegates the actual query creation to the Driver class.) */
317: public SPreparedStatement select(String where, String orderBy,
318: long sqy_bitSet, SFieldMeta[] selectList) {
319: return select(where, orderBy, sqy_bitSet, selectList,
320: new SPreparedStatement());
321: }
322:
323: /** Use this variant to add extra parameters between ps creation and
324: * sql generation. Also use it to subclass ps*/
325: public SPreparedStatement select(String where, String orderBy,
326: long sqy_bitSet, SFieldMeta[] selectList,
327: SPreparedStatement ps) {
328: ps.prepareStatement(this , where, orderBy, sqy_bitSet,
329: selectList == null ? fieldList(sqy_bitSet
330: | SCon.SQY_NO_REFERENCES)
331: : expandSelectList(selectList));
332: return ps;
333: }
334:
335: public SPreparedStatement select(String where, String orderBy,
336: long sqy_bitSet) {
337: return select(where, orderBy, sqy_bitSet, null);
338: }
339:
340: public SPreparedStatement select(String where, String orderBy) {
341: return select(where, orderBy, 0);
342: }
343:
344: /** Start a query using the query builder instead of raw SQL.
345: @see SQuery
346: @see #select
347: */
348: public SQuery newQuery(long sqy_bitSet, SFieldMeta[] selectList) {
349: return new SQuery(this , sqy_bitSet, selectList);
350: }
351:
352: public SQuery newQuery(long sqy_bitSet) {
353: return newQuery(sqy_bitSet, null);
354: }
355:
356: public SQuery newQuery() {
357: return newQuery(0);
358: }
359:
360: public String toString() {
361: return "[SRecordMeta " + SUte.cleanClass(userClass) + "]";
362: }
363:
364: /** Returns a list of all the fields for debugging. */
365: public String allFieldsString() {
366: StringBuffer res = new StringBuffer(this + ":-\n");
367: for (int fx = 0; fx < sFieldMetas.size(); fx++) {
368: res.append(" "
369: + ((SFieldMeta) sFieldMetas.get(fx))
370: .toLongerString() + "\n");
371: }
372: return res.toString();
373: }
374:
375: /**
376: * Returns the (one) generator for the one key of this SRecord.
377: */
378: public SGenerator getSGenerator() {
379: /// Get and check primary key details
380: if (keySFieldMetas.size() != 1)
381: throw new SException.Error(
382: "Must only have one key field if generated " + this );
383: SFieldMeta keyfld = (SFieldMeta) keySFieldMetas.get(0);
384: SGenerator gkey = (SGenerator) keyfld
385: .getProperty(SCon.SGENERATED_KEY);
386: if (gkey == null)
387: throw new SException.Error(keyfld
388: + " is not SGENERATED_KEY");
389:
390: return gkey;
391: }
392:
393: /** Creates a new object with a newly generated key. Note that the
394: SELECT MAX method is used unless a database dependent
395: alternative is available (currently only for PostgreSQL). {@link
396: SGENERATED_KEY}.
397:
398: Always creates a new empty record.
399:
400: ## This code is partially copied into SRecordInstance.attach() for
401: records created while detached.
402: */
403: public SRecordInstance createWithGeneratedKey() {
404: SFieldMeta keyfld = (SFieldMeta) keySFieldMetas.get(0);
405: SRecordInstance newRec = getSGenerator()
406: .createWithGeneratedKey(this , keyfld);
407: return newRec;
408: }
409:
410: /** Flushes and Purges all record instances for this table.
411: <code>SConnection.flushAndPurge()</code> may be more useful in
412: practice.
413:
414: @see SRecordInstance#flushAndPurge
415: @see SConnection#flushAndPurge
416: */
417: public void flushAndPurge() {
418: SConnection scon = SConnection.getBegunConnection();
419: java.util.Iterator ci = scon.transactionCache.values()
420: .iterator();
421: while (ci.hasNext()) {
422: SRecordInstance ri = (SRecordInstance) ci.next();
423: if (ri.getMeta() == this ) {
424: ri.flush();
425: ci.remove(); // #### Is this correct? Deleting cursor?
426: ri.incompleteDestroy();
427: }
428: }
429: }
430:
431: /**
432: * Create an SRecordInstance that will be initially detached. All
433: * key fields must be populated before attaching unless they are
434: * generated keys. Objects created this way
435: * <i>must not</i> not have existed in the database previously, or a unique
436: * key violation exception is thrown by JDBC at flush() time.
437: * <p>
438: * A typical use of this method is by a thick-client application that
439: * creates a record, and then passes it to the application server which, in turn,
440: * will insert it into the database.<p>
441: *
442: * The primary key can be set either using the key parameter,
443: * explicitly after this method using set*, just before it
444: * is reattached and the database is thus available, or if it is
445: * generated then it can be automatically created by the attach()
446: * method.
447: *
448: * @return SRecordInstance A newly created 'blank' record
449: * @see simpleorm.core.SRecordInstance#attach
450: */
451: public SRecordInstance createDetached() {
452: try {
453: SRecordInstance instance = (SRecordInstance) userClass
454: .newInstance();
455: instance.readOnly = false;
456: instance.newRow = true;
457:
458: // Set all nullable fields as if they contained valid data. Without this, objects
459: // created this way become hyper-sensitive, in that even when the data
460: // could legitimately be null, they throw exceptions when you try
461: // to access those fields
462: for (int ii = 0; ii < sFieldMetas.size(); ii++) {
463: //if ( !((SFieldMeta)sFieldMetas.get( ii )).getBoolean( SMANDATORY ) )
464: {
465: instance.bitSets[ii] = SCon.INS_VALID;
466: }
467: }
468:
469: return instance;
470: } catch (Exception ie) {
471: throw new SException.Data(ie);
472: }
473: }
474:
475: /**
476: createDetached() and then sets the key field(s).
477: */
478: public SRecordInstance createDetached(Object key) {
479:
480: SRecordInstance inst = createDetached();
481:
482: SRecordFinder.setPrimaryKeys(inst, key);
483:
484: return inst;
485: }
486:
487: /**
488: * SRecordMeta is like a singleton, in that only one instance of SRecordMeta must
489: * exist in the VM for a specific table. This is a special method used during
490: * de-serialization to determine if the object de-serialized should be substituted.
491: * This method is implemented to return the SRecordMeta object for the appropriate
492: * user Class that already exists.
493: */
494: protected Object readResolve() throws ObjectStreamException {
495: try {
496: Class.forName(userClassName); // This forces class to load and, thus, its SRecordMeta to be instantiated
497:
498: Object substituted = instances_.get(userClassName);
499: if (substituted == null) {
500: throw new NullPointerException();
501: }
502: return substituted;
503: } catch (Exception e) {
504: throw new SException.InternalError(
505: "Error de-serializing SRecordMeta for "
506: + userClassName);
507: }
508: }
509:
510: }
|