001: package simpleorm.core;
002:
003: import java.sql.Connection;
004: import java.sql.PreparedStatement;
005: import java.sql.ResultSet;
006:
007: import simpleorm.core.SException.InternalError;
008: import simpleorm.core.SException.JDBC;
009: import simpleorm.properties.SPropertyValue;
010:
011: /**
012: * Overflow of SRecordMeta code to retrieve records
013: */
014: class SRecordFinder {
015:
016: /**
017: * Implementation of SRecordMeta.findOrCreate
018: */
019: static SRecordInstance findOrCreate(SRecordMeta meta, Object key,
020: long sqy_bitSet, SFieldMeta[] selectList) {
021: SConnection scon = SConnection.getBegunConnection();
022:
023: boolean readOnly = SUte.inBitSet(sqy_bitSet,
024: SCon.SQY_READ_ONLY, SCon.SQY_);
025: boolean unrepeatableRead = SUte.inBitSet(sqy_bitSet,
026: SCon.SQY_UNREPEATABLE_READ, SCon.SQY_);
027: boolean optimistic = SUte.inBitSet(sqy_bitSet,
028: SCon.SQY_OPTIMISTIC, SCon.SQY_)
029: || (!scon.getDriver().supportsLocking() && !readOnly);
030:
031: selectList = meta.expandSelectList(selectList);
032:
033: if (SLog.slog.enableFields())
034: SLog.slog.fields("findOrCreate " + meta + " "
035: + SUte.arrayToString(key)
036: + (readOnly ? " ReadOnly" : "")
037: + (optimistic ? " Optimistic" : ""));
038: if (readOnly && optimistic)
039: throw new SException.Error(
040: "Cannot be both Optimistically Locked and ReadOnly "
041: + meta);
042:
043: /// Create key object. Discarded if found in cache.
044: SRecordInstance keyInstance = null;
045: try {
046: keyInstance = (SRecordInstance) meta.userClass
047: .newInstance();
048: } catch (Exception ie) {
049: throw new SException.Data(ie);
050: }
051: setPrimaryKeys(keyInstance, key);
052:
053: /// See if the record has already been retrieved in this transaction.
054: SRecordInstance instance = (SRecordInstance) scon.transactionCache
055: .get(keyInstance);
056:
057: boolean requery = false;
058:
059: /// If previously retrieved then see if we need to requery.
060: if (instance != null) {
061: instance.wasInCache = true;
062: /// See if we need to requery because there are extra fields.
063: // ### Need to recur if foreign keys.
064: requery = !readOnly && instance.readOnly;
065: for (int sx = 0; sx < selectList.length && !requery; sx++) {
066: SFieldMeta selF = selectList[sx];
067: byte selFSet = instance.bitSets[selF.fieldIndex];
068: requery = requery || ((selFSet & SCon.INS_VALID) == 0);
069: if (!readOnly)
070: requery = requery
071: || ((selFSet & SCon.INS_READ_ONLY) != 0);
072: }
073:
074: if (SLog.slog.enableQueries())
075: SLog.slog.queries("findOrCreated: "
076: + instance
077: + " (from cache)"
078: + (instance.newRow ? " New Row"
079: : " Existing Row")
080: + (requery ? " Requerying..." : ""));
081: if (instance.sConnection != scon)
082: throw new SException.Error("Inconsistent Connections "
083: + instance + instance.sConnection + scon);
084: } else
085: keyInstance.wasInCache = false;
086:
087: /// (re)Query the database
088: if (instance == null || requery) {
089: instance = findInDatabase(meta,
090: instance != null ? instance : keyInstance, // If requery
091: sqy_bitSet, selectList, scon, readOnly, optimistic,
092: unrepeatableRead, null); // ### Need to bring out this props parameter.
093: if (SLog.slog.enableQueries())
094: SLog.slog.queries("findOrCreate: "
095: + instance
096: + " (from database)"
097: + (instance.newRow ? " New Row"
098: : " Existing Row"));
099:
100: if (instance.newRow)
101: validatePrimaryKeys(instance); // Could throw SValidationException
102:
103: scon.transactionCache.put(instance, instance);
104: // equals() is defined below
105: instance.sConnection = scon;
106: }
107: if (instance.getMeta() != meta)
108: throw new SException.InternalError("Found " + instance
109: + " instead of " + meta);
110: return instance;
111: }
112:
113: /** Sets just the primary key fields, used to lookup
114: transactionCache. See equals(). <p>
115:
116: For foreign keys, <code>pkeys</code> only contains the reference
117: object, not the foreign keys themselves.<p>
118:
119: Primary keys are not set dirty.
120: */
121: static void setPrimaryKeys(SRecordInstance inst, Object pkey) {
122: /// If keys is a single key then make it an singleton array.
123: Object[] npkeys = null;
124: int keylen = 1;
125: if (pkey instanceof Object[]) {
126: npkeys = (Object[]) pkey;
127: keylen = npkeys.length;
128: }
129: SRecordMeta meta = inst.getMeta();
130: int kx = -1;
131: for (int mx = 0; mx < meta.keySFieldMetas.size(); mx++) {
132: SFieldMeta keyf = (SFieldMeta) meta.keySFieldMetas.get(mx);
133: if (keyf.sFieldReference == null) { // Top level only
134: kx++;
135:
136: if (keylen < kx + 1)
137: throw new SException.Data("Too few key params "
138: + SUte.arrayToString(pkey));
139:
140: Object rawValue = npkeys != null ? npkeys[kx] : pkey;
141: if (rawValue == null)
142: throw new SException.Data("Null Primary key "
143: + keyf + " " + kx);
144:
145: /// Convert rawValue if necessary.
146: Object convValue = null;
147: try {
148: convValue = keyf.convertToField(rawValue);
149: } catch (Exception ex) {
150: throw new SException.Data("Converting " + rawValue
151: + " for " + inst + "." + keyf, ex);
152: }
153:
154: // For references this will also copy the foreign key values.
155: //SLog.slog.debug("setPrimaryKey " + keyf + this + convValue);
156: keyf.rawSetFieldValue(inst, convValue);
157: }
158: }
159: if (kx + 1 != keylen)
160: throw new SException.Error("Too many key params "
161: + (kx + 1) + " < " + SUte.arrayToString(pkey)
162: + ".length");
163: }
164:
165: /** Look up the database for a record with keys that are now in
166: <code>instance</code> and populate <code>instance</code>
167: appropriately.*/
168: static private SRecordInstance findInDatabase(SRecordMeta meta,
169: SRecordInstance instance, long sqy_bitSet,
170: SFieldMeta[] selectList, SConnection scon,
171: boolean readOnly, boolean optimistic,
172: boolean unrepeatableRead, SPropertyValue[] props) {
173:
174: //System.out.println("findInDatabase " + meta + SLog.arrayToString(selectList));
175: boolean existing = false;
176: if (SUte
177: .inBitSet(sqy_bitSet, SCon.SQY_ASSUME_CREATE, SCon.SQY_)) {
178: existing = false;
179: } else {
180: /// Determine the SQL Query
181: String qry = scon.sDriver.selectSQL(selectList, meta,
182: meta.fieldList(SCon.SQY_PRIMARY_KEY
183: | SCon.SQY_NO_REFERENCES), // for WHERE
184: null, !readOnly && !optimistic, unrepeatableRead,
185: null);
186:
187: if (SLog.slog.enableQueries())
188: SLog.slog.queries("findOrCreate querying '" + qry
189: + "'...");
190:
191: /// Prepare the statement
192: Connection con = scon.jdbcConnection;
193: PreparedStatement ps = null;
194: ResultSet rs = null;
195: try {
196: try {
197: ps = con.prepareStatement(qry); // Let the JDBC driver cache these.
198: } catch (Exception psex) {
199: throw new SException.JDBC(
200: "Preparing '" + qry + "'", psex);
201: }
202: /// Set the primary key
203: int qkx = 0;
204: for (int kx = 0; kx < meta.keySFieldMetas.size(); kx++) {
205: SFieldMeta key = (SFieldMeta) meta.keySFieldMetas
206: .get(kx);
207: if (!(key instanceof SFieldReference)) {
208: qkx++;
209: Object value = instance.fieldValues[key.fieldIndex];
210: try {
211: ps.setObject(qkx, value);
212: } catch (Exception se) {
213: throw new SException.JDBC(
214: "Setting preQuery " + instance
215: + "'" + qry + "' Field "
216: + qkx + " to " + value, se);
217: }
218: }
219: }
220: /// Execute the Query
221: try {
222: rs = ps.executeQuery();
223: } catch (Exception rsex) {
224: throw new SException.JDBC("Executing '" + qry
225: + "' for " + instance, rsex);
226: }
227:
228: /// Retrieve the result.
229: try {
230: existing = rs.next();
231: } catch (Exception ne1) {
232: throw new SException.JDBC("Nexting " + instance,
233: ne1);
234: }
235: if (existing) {
236: SRecordFinder.retrieveRecord(instance, selectList,
237: rs, readOnly, optimistic, true);
238: boolean next = false;
239: try {
240: next = rs.next();
241: } catch (Exception ne2) {
242: throw new SException.JDBC(ne2);
243: }
244: if (next)
245: throw new SException.JDBC(
246: "Primary key not unique " + instance);
247: }
248: } finally {
249: try {
250: if (rs != null)
251: rs.close();
252: } catch (Exception cl1) {
253: throw new SException.JDBC("Closing rs " + instance,
254: cl1);
255: }
256: try {
257: if (ps != null)
258: ps.close();
259: } catch (Exception clp) {
260: throw new SException.JDBC("Closing ps " + instance,
261: clp);
262: }
263: }
264: }
265:
266: if (!existing) { // not in database
267: instance.newRow = true;
268: instance.readOnly = readOnly;
269: /// Make all fields settable.
270: // (Need to do this so still setable after a flush())
271: // New rows have a hard gettable default of null.
272: for (int ifx = 0; ifx < meta.sFieldMetas.size(); ifx++) {
273: SFieldMeta iff = (SFieldMeta) meta.sFieldMetas.get(ifx);
274: //if ( !iff.getBoolean( SMANDATORY ) )
275: instance.bitSets[iff.fieldIndex] |= SCon.INS_VALID;
276: }
277: }
278:
279: // Null the primary-key references, since they may be detached
280: // references, which does not make sense in an attached instance.
281: // (A subsequent getReference would do a findOrCreate.)
282: //
283: // ##Bartek. I think this is now redundant given getRawFieldValue suppresses
284: // references to detached records.
285: for (int ifx = 0; ifx < meta.sFieldMetas.size(); ifx++) {
286: if (meta.sFieldMetas.get(ifx) instanceof SFieldReference) {
287: SRecordInstance keyr = (SRecordInstance) instance.fieldValues[ifx];
288: if (keyr != null
289: && (!keyr.isValid() || !keyr.isAttached()))
290: instance.fieldValues[ifx] = null;
291: }
292: }
293:
294: return instance;
295: } // findInDatabase
296:
297: /**
298: * Map SRecordInstance.validateField for all primary keys.
299: * This is only called for newly created records, not for each find,
300: * which is why it needs to be done in a different pass.
301: */
302: static void validatePrimaryKeys(SRecordInstance inst) {
303: /// If keys is a single key then make it an singleton array.
304: SRecordMeta meta = inst.getMeta();
305: for (int mx = 0; mx < meta.keySFieldMetas.size(); mx++) {
306: SFieldMeta keyf = (SFieldMeta) meta.keySFieldMetas.get(mx);
307: if (keyf.sFieldReference == null) { // Top level only
308: Object valu = keyf.getFieldValue(inst, 0);
309: inst.validateField(keyf, valu);
310: }
311: }
312: }
313:
314: /** Called from SRecordMeta and SResultSet to acutally retrieve the
315: record. <code>selectList</code> includes primary key fields.
316: Calls <code>SField*.queryVieldValue</code> to actually
317: retrieve the values.*/
318: static void retrieveRecord(SRecordInstance instance,
319: SFieldMeta[] selectList, ResultSet rs, boolean readOnly,
320: boolean optimistic, boolean checkPrimaryKey) {
321: /// Retrieve each fieldValue[]
322: for (int fx = 0; fx < selectList.length; fx++) {
323: SFieldMeta fMeta = selectList[fx];
324: int ffx = fMeta.fieldIndex;
325: Object qvalue = null;
326: try {
327: qvalue = fMeta.queryFieldValue(rs, fx + 1);
328: } catch (Exception ge) {
329: throw new SException.JDBC("Getting Field " + (fx + 1)
330: + " from " + instance, ge);
331: }
332: if (checkPrimaryKey && fMeta.isPrimaryKey)
333: if (qvalue == null
334: || !trimStringEquals(qvalue,
335: instance.fieldValues[ffx]))
336: throw new SException.InternalError("Bad PKey "
337: + qvalue.getClass() + " '" + qvalue
338: + "' !equal() '"
339: + instance.fieldValues[ffx].getClass()
340: + "' " + instance.fieldValues[ffx]);
341: // ## This is dubious. What if they are the same except for trailing spaces?
342: // This test will pass, but in general the records will not be considered the same
343: // as we do not generally trim spaces.
344: // See SRecordInstance.getString.
345: instance.fieldValues[ffx] = qvalue;
346: instance.readOnly = readOnly;
347: if (readOnly
348: && (instance.bitSets[ffx] & SCon.INS_VALID) == 0)
349: // If it has previously been retrieved for update do not set ReadOnly now.
350: instance.bitSets[ffx] |= SCon.INS_READ_ONLY;
351: else if (!readOnly)
352: instance.bitSets[ffx] &= ~SCon.INS_READ_ONLY;
353: instance.bitSets[ffx] |= SCon.INS_VALID;
354: }
355:
356: /// Update instance flags
357: instance.readOnly = readOnly;
358: if (optimistic)
359: instance.setOptimistic(false);
360: }
361:
362: /** returns first.rightTrim.equals.rightTrim(second) */
363: static private boolean trimStringEquals(Object firstObj,
364: Object secondObj) {
365: if (!(firstObj instanceof String))
366: return firstObj.equals(secondObj);
367: String first = (String) firstObj, second = (String) secondObj;
368: if (second == null)
369: return false;
370: int fx = first.length() - 1, sx = second.length() - 1;
371: boolean trimed = false;
372: for (; fx > -1 && first.charAt(fx) == ' '; fx--)
373: trimed = true;
374: for (; sx > -1 && second.charAt(sx) == ' '; sx--)
375: trimed = true;
376: if (fx != sx)
377: return false;
378: if (!trimed)
379: return first.equals(second); // Optimization
380: for (; fx > -1; fx--)
381: if (first.charAt(fx) != second.charAt(fx))
382: return false;
383: return true;
384: }
385:
386: }
|