diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Cursor.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Cursor.java new file mode 100644 index 00000000..e39f1bf2 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Cursor.java @@ -0,0 +1,240 @@ +//-< Cursor.java >---------------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Cursor for navigation thru result of SELECT statement +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +import java.util.*; +import java.lang.reflect.*; +import java.sql.*; + +/** Cursor is used for successive access to records fetched by SELECT + * statement. As far as records can be retrived from several derived tables + * (polymorphic form of select), this class can issue several requests + * to database. Cursor also provides methods for updating/deleting current + * record. + */ +public class Cursor { + /** + * A cursor is initially positioned before its first row; the + * first call to next makes the first row the current row; the + * second call makes the second row the current row, etc. + * + *

If an input stream from the previous row is open, it is + * implicitly closed. The ResultSet's warning chain is cleared + * when a new row is read. + * + * @return object constructed from fetched record or null if there + * are no more rows + */ + public Object next() + throws SQLException + { +// try { + do { + if (result == null) { + if (table.isAbstract) { + table = table.derived; + continue; + } + if (qbeObject != null) { + String sql = "select " + table.listOfFields + + " from " + table.name + " " + query; + PreparedStatement qbeStmt; + synchronized(session.preparedStmtHash) { + Object s = session.preparedStmtHash.get(sql); + if (s == null) { + qbeStmt=session.connection.prepareStatement(sql); + session.preparedStmtHash.put(sql, qbeStmt); + } else { + qbeStmt = (PreparedStatement)s; + } + } + synchronized(qbeStmt) { + table.bindQueryVariables(qbeStmt, qbeObject); + result = qbeStmt.executeQuery(); + qbeStmt.clearParameters(); + } + } else { + if (stmt == null) { + stmt = session.connection.createStatement(); + } + String sql = "select " + table.listOfFields + + " from " + table.name + " " + query; + result = stmt.executeQuery(sql); + } + } + if (result.next()) { + return currObject = table.load(result); + } + result.close(); + result = null; + currObject = null; + table = table.derived; + } while (--nTables != 0); + + if (stmt != null) { + stmt.close(); + } +// } +// catch (SQLException ex) { session.handleSQLException(ex); } + return null; + } + + /** Update current record pointed by cursor. This method can be called + * only after next() method, which returns non-null object. This objects + * is used to update current record fields.

+ * + * If you are going to update or delete selected records, you should add + * "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: + * record.table.Select("where name='xyz' for update");

+ * + * Attention! + * Not all database drivers support update operation with + * cursor. This method will not work with such database drivers. + */ + public void update() + throws SQLException + { + if (currObject == null) { + throw new NoCurrentObjectError(); + } +// try { + table.updateVariables(result, currObject); +// } +// catch (SQLException ex) { session.handleSQLException(ex); } + } + + /** Delete current record pointed by cursor. This method can be called + * only after next() method, which returns non-null object.

+ * + * If you are going to update or delete selected records, you should add + * "for update" clause to select statement. So parameter of + * jora.Table.select() statement should contain "for update" + * clause: + * record.table.Select("where name='xyz' for update");

+ * + * Attention! + * Not all database drivers support delete operation with cursor. + * This method will not work with such database drivers. + */ + public void delete() + throws SQLException + { + if (currObject == null) { + throw new NoCurrentObjectError(); + } +// try { + result.deleteRow(); +// } +// catch (SQLException ex) { session.handleSQLException(ex); } + } + + /** Extracts no more than maxElements records from database and + * store them into array. It is possible to extract rest records + * by successive next() or toArray() calls. Selected objects should + * have now components of InputStream, Blob or Clob type, because + * their data will be not available after fetching next record. + * + * @param maxElements limitation for result array size (and also for number + * of fetched records) + * @return Array with objects constructed from fetched records. + */ + public Object[] toArray(int maxElements) + throws SQLException + { + Vector v = new Vector(maxElements < 100 ? maxElements : 100); + Object o; + while (--maxElements >= 0 && (o = next()) != null) { + v.addElement(o); + } + Object[] a = new Object[v.size()]; + v.copyInto(a); + return a; + } + + /** Store all objects returned by SELECT query into array of Object. + * Selected objects should have now components of InputStream, Blob or + * Clob type, because their data will be not available after fetching + * next record. + * + * @return Array with objects constructed from fetched records. + */ + public Object[] toArray() + throws SQLException + { return toArray(Integer.MAX_VALUE); } + + /** Extracts no more than maxElements records from database and + * store them into array. It is possible to extract rest records + * by successive next() or toArray() calls. Selected objects should + * have now components of InputStream, Blob or Clob type, because + * their data will be not available after fetching next record. + * + * @param maxElements limitation for result array size (and also for number + * of fetched records) + * @return List with objects constructed from fetched records. + */ + public List toArrayList(int maxElements) + throws SQLException + { + ArrayList al = new ArrayList(maxElements < 100 ? maxElements : 100); + Object o; + while (--maxElements >= 0 && (o = next()) != null) { + al.add(o); + } + return al; + } + + /** Store all objects returned by SELECT query into a list of Object. + * Selected objects should have now components of InputStream, Blob or + * Clob type, because their data will be not available after fetching + * next record. + * + * @return Array with objects constructed from fetched records. + */ + public List toArrayList() + throws SQLException + { return toArrayList(Integer.MAX_VALUE); } + + // Internals + + protected Cursor(Table table, Session session, int nTables, String query) { + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + this.table = table; + this.session = session; + this.nTables = nTables; + this.query = query; + } + + protected Cursor(Table table, Session session, int nTables, Object obj) { + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + this.table = table; + this.session = session; + this.nTables = nTables; + qbeObject = obj; + query = table.buildQueryList(obj); + stmt = null; + } + + private Table table; + private Session session; + private int nTables; + private ResultSet result; + private String query; + private Statement stmt; + private Object currObject; + private Object qbeObject; +} + diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/DataTransferError.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/DataTransferError.java new file mode 100644 index 00000000..1dc277ad --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/DataTransferError.java @@ -0,0 +1,26 @@ +//-< DataTransferError.java >----------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 19-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Exception raised when error is happed during data transfer between +// program and database server +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +/** This error is raised when error is happened during data transfer + * between program and database server (for example IOException was thrown + * while operation with InputStream field) + */ +public class DataTransferError extends java.lang.Error { + DataTransferError() { + super("Database data transfer error"); + } + DataTransferError(Exception ex) { + super(ex.getMessage()); + } +} + diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java new file mode 100644 index 00000000..03f96c9f --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/FieldDescriptor.java @@ -0,0 +1,437 @@ +//-< FieldDescriptor.java >------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 25-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Table field descriptor +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +import java.sql.*; +import java.math.*; +import java.lang.reflect.*; + +class FieldDescriptor { + protected int inType; // type tag for field input (see constants below) + protected int outType; // type tag for field output + protected int scale; // scale for tDecimal type, + protected String name; // full (compound) name of component + protected Field field; // field info from java.lang.reflect + + protected Constructor constructor; // constructor of object component + + protected static final int t_byte = 0; + protected static final int t_short = 1; + protected static final int t_int = 2; + protected static final int t_long = 3; + protected static final int t_float = 4; + protected static final int t_double = 5; + protected static final int t_boolean = 6; + protected static final int tByte = 7; + protected static final int tShort = 8; + protected static final int tInteger = 9; + protected static final int tLong = 10; + protected static final int tFloat = 11; + protected static final int tDouble = 12; + protected static final int tBoolean = 13; + protected static final int tDecimal = 14; + protected static final int tString = 15; + protected static final int tBytes = 16; + protected static final int tDate = 17; + protected static final int tTime = 18; + protected static final int tTimestamp = 19; + protected static final int tStream = 20; + protected static final int tBlob = 21; + protected static final int tClob = 22; + protected static final int tAsString = 23; + + protected static final int tClosure = 24; + protected static final int tCompound = 25; + + protected static final int[] sqlTypeMapping = { + Types.INTEGER, // t_byte + Types.INTEGER, // t_short + Types.INTEGER, // t_int + Types.BIGINT, // t_long + Types.FLOAT, // t_float + Types.DOUBLE, // t_double + Types.BIT, // t_boolean + Types.INTEGER, // tByte + Types.INTEGER, // tShort + Types.INTEGER, // tInteger + Types.BIGINT, // tLong + Types.FLOAT, // tFloat + Types.DOUBLE, // tDouble + Types.BIT, // tBoolean + Types.NUMERIC, // tDecimal + Types.VARCHAR, // tString + Types.VARBINARY,// tBytes + Types.DATE, // tDate + Types.TIME, // tTime + Types.TIMESTAMP, // tTimestamp + Types.LONGVARBINARY, // tStream + Types.LONGVARBINARY, // tBlob + Types.LONGVARCHAR, // tClob + Types.VARCHAR, // tAsString + Types.LONGVARBINARY // tClosure + }; + + protected FieldDescriptor(Field field, String name) { + this.name = name; + this.field = field; + this.scale = -1; + } + + protected final boolean isAtomic() { return inType < tClosure; } + + protected final boolean isCompound() { return inType >= tCompound; } + + protected final boolean isBuiltin() { return inType <= t_boolean; } + + + protected final boolean bindVariable(PreparedStatement pstmt, + Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + pstmt.setByte(column, field.getByte(obj)); + break; + case t_short: + pstmt.setShort(column, field.getShort(obj)); + break; + case t_int: + pstmt.setInt(column, field.getInt(obj)); + break; + case t_long: + pstmt.setLong(column, field.getLong(obj)); + break; + case t_float: + pstmt.setFloat(column, field.getFloat(obj)); + break; + case t_double: + pstmt.setDouble(column, field.getDouble(obj)); + break; + case t_boolean: + pstmt.setBoolean(column, field.getBoolean(obj)); + break; + case tByte: + pstmt.setByte(column, ((Byte)field.get(obj)).byteValue()); + break; + case tShort: + pstmt.setShort(column, ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + pstmt.setInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + pstmt.setLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + pstmt.setFloat(column, ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + pstmt.setDouble(column,((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + pstmt.setBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + pstmt.setBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + pstmt.setString(column, (String)field.get(obj)); + break; + case tBytes: + pstmt.setBytes(column, (byte[])field.get(obj)); + break; + case tDate: + pstmt.setDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + pstmt.setTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + pstmt.setBinaryStream(column, in, in.available()); + break; + case tBlob: + pstmt.setBlob(column, (Blob)field.get(obj)); + break; + case tClob: + pstmt.setClob(column, (Clob)field.get(obj)); + break; + case tAsString: + pstmt.setString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + pstmt.setBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return bindVariable(pstmt, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean updateVariable(ResultSet result, + Object obj, int column) + throws SQLException + { + try { + switch (outType) { + case t_byte: + result.updateByte(column, field.getByte(obj)); + break; + case t_short: + result.updateShort(column, field.getShort(obj)); + break; + case t_int: + result.updateInt(column, field.getInt(obj)); + break; + case t_long: + result.updateLong(column, field.getLong(obj)); + break; + case t_float: + result.updateFloat(column, field.getFloat(obj)); + break; + case t_double: + result.updateDouble(column, field.getDouble(obj)); + break; + case t_boolean: + result.updateBoolean(column, field.getBoolean(obj)); + break; + case tByte: + result.updateByte(column, ((Byte)field.get(obj)).byteValue()); + break; + case tShort: + result.updateShort(column, + ((Short)field.get(obj)).shortValue()); + break; + case tInteger: + result.updateInt(column, ((Integer)field.get(obj)).intValue()); + break; + case tLong: + result.updateLong(column, ((Long)field.get(obj)).longValue()); + break; + case tFloat: + result.updateFloat(column, + ((Float)field.get(obj)).floatValue()); + break; + case tDouble: + result.updateDouble(column, + ((Double)field.get(obj)).doubleValue()); + break; + case tBoolean: + result.updateBoolean(column, + ((Boolean)field.get(obj)).booleanValue()); + break; + + case tDecimal: + result.updateBigDecimal(column, (BigDecimal)field.get(obj)); + break; + case tString: + result.updateString(column, (String)field.get(obj)); + break; + case tBytes: + result.updateBytes(column, (byte[])field.get(obj)); + break; + case tDate: + result.updateDate(column, (java.sql.Date)field.get(obj)); + break; + case tTime: + result.updateTime(column, (java.sql.Time)field.get(obj)); + break; + case tTimestamp: + result.updateTimestamp(column, + (java.sql.Timestamp)field.get(obj)); + break; + case tStream: + java.io.InputStream in = (java.io.InputStream)field.get(obj); + result.updateBinaryStream(column, in, in.available()); + break; + case tBlob: + Blob blob = (Blob)field.get(obj); + result.updateBinaryStream(column, + blob.getBinaryStream(), + (int)blob.length()); + break; + case tClob: + Clob clob = (Clob)field.get(obj); + result.updateCharacterStream(column, + clob.getCharacterStream(), + (int)clob.length()); + break; + case tAsString: + result.updateString(column, field.get(obj).toString()); + break; + case tClosure: + // There is no reason to use piped streams because + // we need to pass total number of bytes to JDBC driver + java.io.ByteArrayOutputStream out = + new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream clu = + new java.io.ObjectOutputStream(out); + clu.writeObject(field.get(obj)); + clu.close(); + result.updateBytes(column, out.toByteArray()); + break; + default: + return false; + } + } catch(SQLException ex) { + if (outType != tClosure && outType != tAsString) { + outType = tAsString; + return updateVariable(result, obj, column); + } else { + throw ex; + } + } catch(IllegalAccessException ex) { + ex.printStackTrace(); + throw new IllegalAccessError(); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + return true; + } + + protected final boolean loadVariable(ResultSet result, + Object obj, int column) + throws SQLException, IllegalAccessException + { + switch (inType) { + case t_byte: + field.setByte(obj, result.getByte(column)); + break; + case t_short: + field.setShort(obj, result.getShort(column)); + break; + case t_int: + field.setInt(obj, result.getInt(column)); + break; + case t_long: + field.setLong(obj, result.getLong(column)); + break; + case t_float: + field.setFloat(obj, result.getFloat(column)); + break; + case t_double: + field.setDouble(obj, result.getDouble(column)); + break; + case t_boolean: + field.setBoolean(obj, result.getBoolean(column)); + break; + + case tByte: + byte b = result.getByte(column); + field.set(obj, result.wasNull() ? null : new Byte(b)); + break; + case tShort: + short s = result.getShort(column); + field.set(obj, result.wasNull() ? null : new Short(s)); + break; + case tInteger: + int i = result.getInt(column); + field.set(obj, result.wasNull() ? null : new Integer(i)); + break; + case tLong: + long l = result.getLong(column); + field.set(obj, result.wasNull() ? null : new Long(l)); + break; + case tFloat: + float f = result.getFloat(column); + field.set(obj, result.wasNull() ? null : new Float(f)); + field.setFloat(obj, result.getFloat(column)); + break; + case tDouble: + double d = result.getDouble(column); + field.set(obj, result.wasNull() ? null : new Double(d)); + break; + case tBoolean: + boolean bl = result.getBoolean(column); + field.set(obj, result.wasNull() ? null : new Boolean(bl)); + break; + + case tDecimal: + if (Table.useDepricatedGetBigDecimal) { + if (scale < 0) { + scale = result.getMetaData().getScale(column); + } + field.set(obj, result.getBigDecimal(column, scale)); + } else { + field.set(obj, result.getBigDecimal(column)); + } + break; + case tString: + field.set(obj, result.getString(column)); + break; + case tBytes: + field.set(obj, result.getBytes(column)); + break; + case tDate: + field.set(obj, result.getDate(column)); + break; + case tTime: + field.set(obj, result.getTime(column)); + break; + case tTimestamp: + field.set(obj, result.getTimestamp(column)); + break; + case tStream: + field.set(obj, result.getBinaryStream(column)); + break; + case tBlob: + field.set(obj, result.getBlob(column)); + break; + case tClob: + field.set(obj, result.getClob(column)); + break; + case tClosure: + try { + java.io.InputStream input = result.getBinaryStream(column); + java.io.ObjectInputStream in = + new java.io.ObjectInputStream(input); + field.set(obj, in.readObject()); + in.close(); + } catch(ClassNotFoundException ex) { + throw new DataTransferError(ex); + } catch(java.io.IOException ex) { + throw new DataTransferError(ex); + } + break; + default: + return false; + } + return true; + } +} + diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Makefile b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Makefile new file mode 100644 index 00000000..9585e350 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Makefile @@ -0,0 +1,17 @@ +# +# $Id: Makefile,v 1.1 2001/02/13 05:46:56 mdb Exp $ + +ROOT = ../../../.. + +SRCS = \ + Cursor.java \ + DataTransferError.java \ + FieldDescriptor.java \ + NoCurrentObjectError.java \ + NoPrimaryKeyError.java \ + SQLError.java \ + Session.java \ + SessionThread.java \ + Table.java \ + +include $(ROOT)/build/Makefile.java diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java new file mode 100644 index 00000000..a0e141a2 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoCurrentObjectError.java @@ -0,0 +1,23 @@ +//-< NoCurrentObjectError.java >-------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Exception raised when UPDATE/REMOVE operation is applied to Cursor +// with no current object +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +/** Error raised when update/remove operation is applied to + * cursor with no current object (before first call of next() + * method). + */ +public class NoCurrentObjectError extends java.lang.Error { + NoCurrentObjectError() { + super("Cursor doesn't specify current object"); + } +} + diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java new file mode 100644 index 00000000..4ed703dc --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/NoPrimaryKeyError.java @@ -0,0 +1,22 @@ +//-< NoPrimaryKeyError.java >----------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Exception raised when UPDATE/REMOVE operation is appllied to Table with +// no primary key +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +/** Error raised when unpdate/remove operation is invoked for the table + * with no correct primary key defined (key was not specified or + * type of the key component is not atomic). + */ +public class NoPrimaryKeyError extends java.lang.Error { + NoPrimaryKeyError(Table table) { + super("Table " + table.name + " has no atomic primary key"); + } +} diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/README b/projects/samskivert/src/java/com/samskivert/jdbc/jora/README new file mode 100644 index 00000000..8fdd810a --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/README @@ -0,0 +1,27 @@ +What have we here? +------------------ + +This is a hacked version of JORA, a library for automatically mapping +RDBMS database rows to Java objects and vice versa. It's simple, it uses +reflection and doesn't require any classfile post-processing or external +configuration information. + +"That's all great but why is it hacked?" wonders the astute reader. Well, +I couldn't quite cope with the error handling paradigm that it used (catch +all SQL exceptions internally and pass them to an application-wide error +handler method) and a few seconds thought on the matter led me to believe +that there is not a simple way to allow a choice of error handling +paradigms (the methods have to throw SQLException or not). So I took the +easy way out and forked the codebase. + +The code is pretty stable, so I don't expect to be merging in updates with +any frequency. I promise to be a good monkey and submit any useful +modifications that I make back to the original maintainer. It's a good +thing that the license was unrestrictive enough to allow me to do this, +otherwise I'd probably be stuck reinventing the wheel or using something +that does less of what I want. Three cheers for free software. + +The original version can be found here: http://www.ispras.ru/~knizhnik/ +(or at least it could when I wrote this README). + +- mdb (2/12/2001) diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/SQLError.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/SQLError.java new file mode 100644 index 00000000..d2615198 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/SQLError.java @@ -0,0 +1,21 @@ +//-< SQLError.java >-------------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 16-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Database error +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +/** Database error. Exception SQLException was catched by JORA. + */ +public class SQLError extends java.lang.RuntimeException { + java.sql.SQLException ex; + SQLError(java.sql.SQLException x) { + super("Database session aborted due to critical error"); + ex = x; + } +} diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Session.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Session.java new file mode 100644 index 00000000..fd90a1a4 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Session.java @@ -0,0 +1,161 @@ +//-< Session.java >--------------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Database session abstraction +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +import java.util.*; +import java.sql.*; + +/** + * This class is reposnsible for establishing connection with database + * and handling database errors. + */ +public class Session { + public Connection connection; + + /** Session consructor + * + * @param driverClass class of database driver + */ + public Session(String driverClass) + { + driver = driverClass; + preparedStmtHash = new Hashtable(); + connectionID = 0; + } + + /** Session consructor for ODBC bridge driver + */ + public Session() { this("sun.jdbc.odbc.JdbcOdbcDriver"); } + + /** Handler of database session errors. Programmer should override + * this method in derived class in order to provide application + * dependent error handling. + * + * @param ex exception thrown by some of JDBC methods + */ + public void handleSQLException(SQLException ex) { + // A SQLException was generated. Catch it and + // display the error information. Note that there + // could be multiple error objects chained + // together + System.out.println ("*** SQLException caught ***"); + SQLException x = ex; + while (ex != null) { + System.out.println("SQLState: " + ex.getSQLState ()); + System.out.println("Message: " + ex.getMessage ()); + System.out.println("Vendor: " + ex.getErrorCode ()); + ex = ex.getNextException(); + System.out.println (""); + } + throw new SQLError(x); // terminate program execution + } + + /** Open database session. + * Attempt to establish a connection to the given database URL. + * The DriverManager attempts to select an appropriate driver from + * the set of registered JDBC drivers. + * + * @param url a database url of the form + * jdbc:subprotocol:subname + * @param user the database user on whose behalf the Connection is being made + * @param password the user's password + * @return true if session is succesfully openned, false otherwise + */ + public boolean open(String dataSource, String user, String password) + throws SQLException + { + try { + Class.forName(driver); + + connection = + DriverManager.getConnection(dataSource, user, password); + connectionID += 1; + } +// catch(SQLException ex) { +// handleSQLException(ex); +// return false; +// } + catch(ClassNotFoundException ex) { + return false; + } + return true; + } + + /** Close database session and release all resources holded by session. + */ + public void close() + throws SQLException + { +// try { + Enumeration items = preparedStmtHash.elements(); + while (items.hasMoreElements()) { + ((PreparedStatement)items.nextElement()).close(); + } + preparedStmtHash.clear(); + connection.close(); +// } +// catch (SQLException ex) { handleSQLException(ex); } + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, + * SQL statements that return nothing such as SQL DDL statements + * can be executed. + * + * @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL + * statement that returns nothing + * @return either the row count for INSERT, UPDATE or DELETE or 0 + * for SQL statements that return nothing + */ + public int execute(String sql) + throws SQLException + { +// try { + Statement stmt = connection.createStatement(); + int result = stmt.executeUpdate(sql); + stmt.close(); + return result; +// } catch(SQLException ex) { handleSQLException(ex); } +// return -1; + } + + /** + * Commit makes all changes made since the previous + * commit/rollback permanent and releases any database locks + * currently held by the Connection. This method should only be + * used when auto commit has been disabled. + */ + public void commit() + throws SQLException + { +// try { + connection.commit(); +// } catch(SQLException ex) { handleSQLException(ex); } + } + + /** + * Rollback drops all changes made since the previous + * commit/rollback and releases any database locks currently held + * by the Connection. This method should only be used when auto + * commit has been disabled. + */ + public void rollback() + throws SQLException + { +// try { + connection.rollback(); +// } catch(SQLException ex) { handleSQLException(ex); } + } + + protected String driver; // driver class name + protected Hashtable preparedStmtHash; + protected int connectionID; +} diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/SessionThread.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/SessionThread.java new file mode 100644 index 00000000..bf483f78 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/SessionThread.java @@ -0,0 +1,148 @@ +//-< SessionThread.java >--------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 18-Jun-99 K.A. Knizhnik * / [] \ * +// Last update: 18-Jun-99 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Thread associated with database session +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +/** + * Class representing thread associated with users database session. + * If there is single database session opened by application, that it is + * possible to associate it with Table object statically. Otherwise it is + * needed either to explicitly specify session object in each insert,select + * or update statement or associate session with thread by means of + * SessionThread class. + */ +public class SessionThread extends Thread { + Session session; + + /** + * Allocates a new SessionThread object and associate it with + * the specified session. This constructor has the same effect as + * SessionThread(session, null, null, + * gname), where gname is + * a newly generated name. Automatically generated names are of the + * form "Thread-"+n, where n is an integer. + *

+ * Threads created this way must have overridden their + * run() method to actually do anything. + * + * @param session user database session associated with this thread + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session) { + this.session = session; + } + + /** + * Allocates a new SessionThread object and associate it with + * the specified session. This constructor has the same effect as + * SessionThread(session, target, null, + * gname), where gname is + * a newly generated name. Automatically generated names are of the + * form "Thread-"+n, where n is an integer. + *

+ * Threads created this way must have overridden their + * run() method to actually do anything. + * + * @param session user database session associated with this thread + * @param target the object whose run method is called. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, Runnable target) { + super(target); + this.session = session; + } + + /** + * Allocates a new SessionThread object and associate it with + * the specified session. This constructor has the same effect as + * SessionThread(session, target, group, + * gname), where gname is + * a newly generated name. Automatically generated names are of the + * form "Thread-"+n, where n is an integer. + *

+ * Threads created this way must have overridden their + * run() method to actually do anything. + * + * @param session user database session associated with this thread + * @param group the thread group. + * @param target the object whose run method is called. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, ThreadGroup group, Runnable target) { + super(target); + this.session = session; + } + + /** + * Allocates a new SessionThread object. This constructor has + * the same effect as SessionThread(session, null, null, name). + * + * @param session user database session associated with this thread + * @param name the name of the new thread. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, String name) { + super(name); + this.session = session; + } + + /** + * Allocates a new SessionThread object. This constructor has + * the same effect as SessionThread(session, group, null, name). + * + * @param session user database session associated with this thread + * @param group the thread group. + * @param name the name of the new thread. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, ThreadGroup group, String name) { + super(group, name); + this.session = session; + } + + /** + * Allocates a new SessionThread object. This constructor has + * the same effect as SessionThread(session, null, target, name) * + * @param session user database session associated with this thread + * @param target the object whose run method is called. + * @param name the name of the new thread. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, Runnable target, String name) { + super(target, name); + this.session = session; + } + + /** + * Allocates a new SessionThread object so that it has + * target as its run object, has the specified + * name as its name, and belongs to the thread group + * referred to by group. + *

+ * @param session user database session associated with this thread + * @param group the thread group. + * @param target the object whose run method is called. + * @param name the name of the new thread. + * @see java.lang.Thread#Thread(java.lang.ThreadGroup, + * java.lang.Runnable, java.lang.String) + */ + public SessionThread(Session session, ThreadGroup group, Runnable target, + String name) + { + super(group, target, name); + this.session = session; + } +} diff --git a/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java new file mode 100644 index 00000000..e605a699 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/jdbc/jora/Table.java @@ -0,0 +1,977 @@ +//-< Table.java >----------------------------------------------------*--------* +// JORA Version 2.0 (c) 1998 GARRET * ? * +// (Java Object Relational Adapter) * /\| * +// * / \ * +// Created: 10-Jun-98 K.A. Knizhnik * / [] \ * +// Last update: 20-Jun-98 K.A. Knizhnik * GARRET * +//-------------------------------------------------------------------*--------* +// Class representing database table +//-------------------------------------------------------------------*--------* + +package com.samskivert.jdbc.jora; + +import java.util.*; +import java.sql.*; +import java.lang.reflect.*; + +/** Table class is used to establish mapping between corteges of database + * tables and Java classes. This class is responsible for constructing + * SQL statements for extracting, updating and deleting records of the + * database table. + */ +public class Table { + /** Constructor for table object. Make association between Java class + * and database table. + * + * @param tclassName name of Java class + * @param tableName name of database table mapped on this Java class + * @param key table's primary key. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @s session, which should be opened before first access to the table + */ + public Table(String className, String tableName, Session s, String key) { + String[] keys = {key}; + init(className, tableName, s, keys); + } + + /** Constructor for table object. Make association between Java class + * and database table. + * + * @param tclassName name of Java class + * @param tableName name of database table mapped on this Java class + * @param keys table primary keys. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @s session, which should be opened before first access to the table + */ + public Table(String className, String tableName, Session s, String[] keys) + { + init(className, tableName, s, keys); + } + + /** Constructor for table object. Make association between Java class + * and database table. Name of Java class should be the same as name of + * the database table + * + * @param className name of Java class, which should be (without + * package prefix) be the same as the name of database table. + * @param keys table primary keys. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @s session, which should be opened before first access to the table + */ + public Table(String className, Session s, String[] keys) { + init(className, className.substring(className.lastIndexOf('.')+1), + s, keys); + } + + /** Constructor for table object. Make association between Java class + * and database table. Name of Java class should be the same as name of + * the database table + * + * @param className name of Java class, which should be (without + * package prefix) be the same as the name of database table. + * @param key table primary key. This parameter is used in UPDATE/DELETE + * operations to locate record in the table. + * @s session, which should be opened before first access to the table + */ + public Table(String className, Session s, String key) { + String[] keys = {key}; + init(className, className.substring(className.lastIndexOf('.')+1), + s, keys); + } + + /** Constructor of table without explicit key specification. + * Specification of key is necessary for update/remove operations. + * If key is not specified, it is inherited from base table (if any). + */ + public Table(String className, Session s) { + init(className, className.substring(className.lastIndexOf('.')+1), + s, null); + } + + + /** Constructor of table with "key" and "session" parameters inherited + * from base table. + */ + public Table(String className) { + init(className, className.substring(className.lastIndexOf('.')+1), + null, null); + } + + + /** Select records from database table according to search condition + * + * @param condition valid SQL condition expression started with WHERE + * or empty string if all records should be fetched. + */ + public final Cursor select(String condition) { + return new Cursor(this, session, 1, condition); + } + + /** Select records from database table according to search condition + * + * @param condition valid SQL condition expression started with WHERE + * or empty string if all records should be fetched. + * @param session user database session + */ + public final Cursor select(String condition, Session session) { + return new Cursor(this, session, 1, condition); + } + + /** Select records from specified and derived database tables + * + * @param condition valid SQL condition expression started with WHERE + * or empty string if all records should be fetched. + */ + public final Cursor selectAll(String condition) { + return new Cursor(this, session, nDerived+1, condition); + } + + /** Select records from specified and derived database tables + * + * @param condition valid SQL condition expression started with WHERE + * or empty string if all records should be fetched. + * @param session user database session + */ + public final Cursor selectAll(String condition, Session session) { + return new Cursor(this, session, nDerived+1, condition); + } + + + /** Select records from database table using obj object as + * template for selection. All non-builtin fields of this object, + * which are not null, are compared with correspondent table values. + * + * @param obj object for construction search condition: selected objects + * should match all non-null fields of specified object. + */ + public final Cursor queryByExample(Object obj) { + return new Cursor(this, session, 1, obj); + } + + /** Select records from database table using obj object as + * template for selection. All non-builtin fields of this object, + * which are not null, are compared with correspondent table values. + * + * @param obj object for construction search condition: selected objects + * should match all non-null fields of specified object. + * @param session user database session + */ + public final Cursor queryByExample(Object obj, Session session) { + return new Cursor(this, session, 1, obj); + } + + /** Select records from specified and derived database tables using + * obj object as template for selection. + * All non-builtin fields of this object, + * which are not null, are compared with correspondent table values. + * + * @param obj object for construction search condition: selected objects + * should match all non-null fields of specified object. + */ + public final Cursor queryAllByExample(Object obj) { + return new Cursor(this, session, nDerived+1, obj); + } + + /** Select records from specified and derived database tables using + * obj object as template for selection. + * All non-builtin fields of this object, + * which are not null, are compared with correspondent table values. + * + * @param obj object for construction search condition: selected objects + * should match all non-null fields of specified object. + * @param session user database session + */ + public final Cursor queryAllByExample(Object obj, Session session) { + return new Cursor(this, session, nDerived+1, obj); + } + + /** Insert new record in the table. Values of inserted record fields + * are taken from specifed object. + * + * @param obj object specifing values of inserted record fields + */ + public void insert(Object obj) + throws SQLException + { + insert(obj, session); + } + + /** Insert new record in the table using specified database session. + * Values of inserted record fields + * are taken from specifed object. + * + * @param obj object specifing values of inserted record fields + * @param session user database session + */ + public synchronized void insert(Object obj, Session session) + throws SQLException + { + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } +// try { + checkConnection(session); + if (insertStmt == null) { + String sql = "insert into " + name + " (" + + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + insertStmt = session.connection.prepareStatement(sql); + } + bindUpdateVariables(insertStmt, obj); + insertStmt.executeUpdate(); + insertStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + } + + /** Insert several new records in the table. Values of inserted records + * fields are taken from objects of specified array. + * + * @param objects array with objects specifing values of inserted record + * fields + */ + public void insert(Object[] objects) + throws SQLException + { + insert(objects, session); + } + + /** Insert several new records in the table. Values of inserted records + * fields are taken from objects of specified array. + * + * @param objects array with objects specifing values of inserted record + * fields + * @param session user database session + */ + public synchronized void insert(Object[] objects, Session session) + throws SQLException + { + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } +// try { + checkConnection(session); + if (insertStmt == null) { + String sql = "insert into " + name + " (" + + listOfFields + ") values (?"; + for (int i = 1; i < nColumns; i++) { + sql += ",?"; + } + sql += ")"; + insertStmt = session.connection.prepareStatement(sql); + } + for (int i = 0; i < objects.length; i++) { + bindUpdateVariables(insertStmt, objects[i]); + insertStmt.addBatch(); + } + insertStmt.executeBatch(); + insertStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + } + + /** Update record in the table using table's primary key to locate + * record in the table and values of fields of specified object obj + * to alter record fields. + * + * @param obj object specifing value of primary key and new values of + * updated record fields + * + * @return number of objects actually updated + */ + public int update(Object obj) + throws SQLException + { + return update(obj, session); + } + + /** Update record in the table using table's primary key to locate + * record in the table and values of fields of specified object obj + * to alter record fields. + * + * @param obj object specifing value of primary key and new values of + * updated record fields + * @param session user database session + * + * @return number of objects actually updated + */ + public synchronized int update(Object obj, Session session) + throws SQLException + { + if (primaryKeys == null) { + throw new NoPrimaryKeyError(this); + } + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + int nUpdated = 0; +// try { + checkConnection(session); + if (updateStmt == null) { + String sql = "update " + name + " set " + listOfAssignments + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + updateStmt = session.connection.prepareStatement(sql); + } + for (int i = 0; i < primaryKeys.length; i++) { + fields[primaryKeyIndices[i]].bindVariable(updateStmt, obj, + nColumns+i+1); + } + bindUpdateVariables(updateStmt, obj); + nUpdated = updateStmt.executeUpdate(); + updateStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + return nUpdated; + } + + /** Update set of records in the table using table's primary key to locate + * record in the table and values of fields of objects from sepecifed + * array objects to alter record fields. + * + * @param objects array of objects specifing primiray keys and and new + * values of updated record fields + * + * @return number of objects actually updated + */ + public int update(Object[] objects) + throws SQLException + { + return update(objects, session); + } + + /** Update set of records in the table using table's primary key to locate + * record in the table and values of fields of objects from sepecifed + * array objects to alter record fields. + * + * @param objects array of objects specifing primiray keys and and new + * values of updated record fields + * @param session user database session + * + * @return number of objects actually updated + */ + public synchronized int update(Object[] objects, Session session) + throws SQLException + { + if (primaryKeys == null) { + throw new NoPrimaryKeyError(this); + } + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + int nUpdated = 0; +// try { + checkConnection(session); + if (updateStmt == null) { + String sql = "update " + name + " set " + listOfAssignments + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + updateStmt = session.connection.prepareStatement(sql); + } + for (int i = 0; i < objects.length; i++) { + for (int j = 0; j < primaryKeys.length; j++) { + fields[primaryKeyIndices[j]].bindVariable(updateStmt, + objects[i], + nColumns+1+j); + } + bindUpdateVariables(updateStmt, objects[i]); + updateStmt.addBatch(); + } + int rc[] = updateStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nUpdated += rc[k]; + } + updateStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + return nUpdated; + } + + /** Delete record with specified value of primary key from the table. + * + * @param obj object containing value of primary key. + * + * @return number of objects actually deleted + */ + public int delete(Object obj) + throws SQLException + { + return delete(obj, session); + } + + /** Delete record with specified value of primary key from the table. + * + * @param obj object containing value of primary key. + * @param session user database session + */ + public synchronized int delete(Object obj, Session session) + throws SQLException + { + if (primaryKeys == null) { + throw new NoPrimaryKeyError(this); + } + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + int nDeleted = 0; +// try { + checkConnection(session); + if (deleteStmt == null) { + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + deleteStmt = session.connection.prepareStatement(sql); + } + for (int i = 0; i < primaryKeys.length; i++) { + fields[primaryKeyIndices[i]].bindVariable(deleteStmt, obj,i+1); + } + nDeleted = deleteStmt.executeUpdate(); + deleteStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + return nDeleted; + } + + + /** Delete records with specified primary keys from the table. + * + * @param objects array of objects containing values of primary key. + * + * @return number of objects actually deleted + */ + public int delete(Object[] objects) + throws SQLException + { + return delete(objects, session); + } + + /** Delete records with specified primary keys from the table. + * + * @param objects array of objects containing values of primary key. + * + * @return number of objects actually deleted + */ + public synchronized int delete(Object[] objects, Session session) + throws SQLException + { + if (primaryKeys == null) { + throw new NoPrimaryKeyError(this); + } + if (session == null) { + session = ((SessionThread)Thread.currentThread()).session; + } + int nDeleted = 0; +// try { + checkConnection(session); + if (deleteStmt == null) { + String sql = "delete from " + name + + " where " + primaryKeys[0] + " = ?"; + for (int i = 1; i < primaryKeys.length; i++) { + sql += " and " + primaryKeys[i] + " = ?"; + } + deleteStmt = session.connection.prepareStatement(sql); + } + for (int i = 0; i < objects.length; i++) { + for (int j = 0; j < primaryKeys.length; j++) { + fields[primaryKeyIndices[j]].bindVariable(deleteStmt, + objects[i], j+1); + } + deleteStmt.addBatch(); + } + int rc[] = deleteStmt.executeBatch(); + for (int k = 0; k < rc.length; k++) { + nDeleted += rc[k]; + } + deleteStmt.clearParameters(); +// } catch(SQLException ex) { session.handleSQLException(ex); } + return nDeleted; + } + + /** Spearator of name components of compound field. For example, if Java + * class constains component "location" of Point class, which + * has two components "x" and "y", then database table should + * have columns "location_x" and "location_y" (if '_' is used + * as separator) + */ + public static String fieldSeparator = "_"; + + + /** Some versions of JDBC driver doesn't support + * getBigDecimal(int columnIndex). Setting this variable to + * true makes JORA to explicitly request scale from result + * set metadata and use deprecated version of getBigDecimal + * with extra scale parameter. + */ + public static boolean useDepricatedGetBigDecimal = true; + + + // --- Implementation ----------------------------------------- + + /** Is table abstract - not present in database. + */ + protected boolean isAbstract; + protected Table derived; + protected int nDerived; + + protected String name; + protected String listOfFields; + protected String listOfAssignments; + protected Class cls; + protected Session session; + + static private Class serializableClass; + private FieldDescriptor[] fields; + + private int nFields; // length of "fields" array + private int nColumns; // number of atomic fields in "fields" array + + private String primaryKeys[]; + private int primaryKeyIndices[]; + + protected int connectionID; + + private PreparedStatement updateStmt; + private PreparedStatement deleteStmt; + private PreparedStatement insertStmt; + + private static Table allTables; + private Constructor constructor; + private static Method setBypass; + + private static final Object[] bypassFlag = { new Boolean(true) }; + private static final Object[] constructorArgs = {}; + + static { + try { + serializableClass = Class.forName("java.io.Serializable"); + Class c = Class.forName("java.lang.reflect.AccessibleObject"); + Class[] param = { Boolean.TYPE }; + setBypass = c.getMethod("setAccessible", param); + } catch(Exception ex) {} + } + + + private final void init(String className, String tableName, Session s, + String[] keys) + { + name = tableName; + try { + cls = Class.forName(className); + } catch(ClassNotFoundException ex) {throw new NoClassDefFoundError();} + isAbstract = tableName == null; + session = s; + primaryKeys = keys; + listOfFields = ""; + listOfAssignments = ""; + connectionID = 0; + Vector fieldsVector = new Vector(); + nFields = buildFieldsList(fieldsVector, cls, ""); + fields = new FieldDescriptor[nFields]; + fieldsVector.copyInto(fields); + + try { + constructor = cls.getDeclaredConstructor(new Class[0]); + setBypass.invoke(constructor, bypassFlag); + } catch(Exception ex) {} + + if (keys != null) { + if (keys.length == 0) { + throw new NoPrimaryKeyError(this); + } + primaryKeyIndices = new int[keys.length]; + for (int j = keys.length; --j >= 0;) { + int i = nFields; + while (--i >= 0) { + if (fields[i].name.equals(keys[j])) { + if (!fields[i].isAtomic()) { + throw new NoPrimaryKeyError(this); + } + primaryKeyIndices[j] = i; + break; + } + } + if (i < 0) { + throw new NoSuchFieldError("No such field '" + keys[j] + + "' in table " + name); + } + } + } + insertIntoTableHierarchy(); + } + + + private final void insertIntoTableHierarchy() + { + Table t, prev = null; + Table after = null; + int nChilds = 0; + for (t = allTables; t != null; prev = t, t = t.derived) { + if (t.cls.isAssignableFrom(cls)) { + if (primaryKeys == null && t.primaryKeys != null) { + primaryKeys = t.primaryKeys; + primaryKeyIndices = t.primaryKeyIndices; + } + if (session == null) { + session = t.session; + } + t.nDerived += 1; + after = t; + } else if (cls.isAssignableFrom(t.cls)) { + after = prev; + do { + if (cls.isAssignableFrom(t.cls)) { + if (primaryKeys != null && t.primaryKeys == null) { + t.primaryKeys = primaryKeys; + t.primaryKeyIndices = primaryKeyIndices; + } + if (t.session == null) { + t.session = session; + } + nChilds += 1; + } + } while ((t = t.derived) != null); + break; + } + } + if (after == null) { + derived = allTables; + allTables = this; + } else { + derived = after.derived; + after.derived = this; + } + nDerived = nChilds; + } + + private final void checkConnection(Session s) throws SQLException { + if (connectionID != s.connectionID) { + if (insertStmt != null) { + insertStmt.close(); + insertStmt = null; + } + if (updateStmt != null) { + updateStmt.close(); + updateStmt = null; + } + if (deleteStmt != null) { + deleteStmt.close(); + deleteStmt = null; + } + connectionID = s.connectionID; + } + } + + + private final int buildFieldsList(Vector buf, Class cls, String prefix) + { + Field[] f = cls.getDeclaredFields(); + + Class superclass = cls; + while ((superclass = superclass.getSuperclass()) != null) { + Field[] inheritedFields = superclass.getDeclaredFields(); + Field[] allFields = new Field[inheritedFields.length + f.length]; + System.arraycopy(inheritedFields, 0, allFields, 0, + inheritedFields.length); + System.arraycopy(f,0, allFields, inheritedFields.length, f.length); + f = allFields; + } + + try { + for (int i = f.length; --i>= 0;) { + setBypass.invoke(f[i], bypassFlag); + } + } catch(Exception ex) { + System.err.println("Failed to set bypass attribute"); + } + + int n = 0; + for (int i = 0; i < f.length; i++) { + if ((f[i].getModifiers()&(Modifier.TRANSIENT|Modifier.STATIC))==0) + { + String name = f[i].getName(); + Class fieldClass = f[i].getType(); + String fullName = prefix + name; + FieldDescriptor fd = new FieldDescriptor(f[i], fullName); + int type; + + buf.addElement(fd); + n += 1; + + String c = fieldClass.getName(); + if (c.equals("byte")) type = FieldDescriptor.t_byte; + else if (c.equals("short")) type = FieldDescriptor.t_short; + else if (c.equals("int")) type = FieldDescriptor.t_int; + else if (c.equals("long")) type = FieldDescriptor.t_long; + else if (c.equals("float")) type = FieldDescriptor.t_float; + else if (c.equals("double")) type = FieldDescriptor.t_double; + else if (c.equals("boolean")) type = FieldDescriptor.t_boolean; + else if (c.equals("java.lang.Byte")) + type = FieldDescriptor.tByte; + else if (c.equals("java.lang.Short")) + type = FieldDescriptor.tShort; + else if (c.equals("java.lang.Integer")) + type = FieldDescriptor.tInteger; + else if (c.equals("java.lang.Long")) + type = FieldDescriptor.tLong; + else if (c.equals("java.lang.Float")) + type = FieldDescriptor.tFloat; + else if (c.equals("java.lang.Double")) + type = FieldDescriptor.tDouble; + else if (c.equals("java.lang.Boolean")) + type = FieldDescriptor.tBoolean; + else if (c.equals("java.math.BigDecimal")) + type = FieldDescriptor.tDecimal; + else if (c.equals("java.lang.String")) + type = FieldDescriptor.tString; + else if (c.equals("java.lang.[B")) + type = FieldDescriptor.tBytes; + else if (c.equals("java.sql.Date")) + type = FieldDescriptor.tDate; + else if (c.equals("java.sql.Time")) + type = FieldDescriptor.tTime; + else if (c.equals("java.sql.Timestamp")) + type = FieldDescriptor.tTimestamp; + else if (c.equals("java.lang.InputStream")) + type = FieldDescriptor.tStream; + else if (c.equals("java.sql.BlobLocator")) + type = FieldDescriptor.tBlob; + else if (c.equals("java.sql.ClobLocator")) + type = FieldDescriptor.tClob; + else if (serializableClass.isAssignableFrom(fieldClass)) + type = FieldDescriptor.tClosure; + else { + int nComponents = buildFieldsList(buf, fieldClass, + fd.name+fieldSeparator); + fd.inType = fd.outType = + FieldDescriptor.tCompound + nComponents; + + try { + fd.constructor = + fieldClass.getDeclaredConstructor(new Class[0]); + setBypass.invoke(fd.constructor, bypassFlag); + } catch(Exception ex) {} + + n += nComponents; + continue; + } + if (listOfFields.length() != 0) { + listOfFields += ","; + listOfAssignments += ","; + } + listOfFields += fullName; + listOfAssignments += fullName + "=?"; + + fd.inType = fd.outType = type; + nColumns += 1; + } + } + return n; + } + + + protected final Object load(ResultSet result) throws SQLException { + Object obj; + try { + obj = constructor.newInstance(constructorArgs); + } + catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + catch(InstantiationException ex) { throw new InstantiationError(); } + catch(Exception ex) { + throw new InstantiationError("Exception was thrown by constructor"); + } + load(obj, 0, nFields, 0, result); + return obj; + } + + private final int load(Object obj, int i, int end, int column, + ResultSet result) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + if (!fd.loadVariable(result, obj, ++column)) { + Object component = + fd.constructor.newInstance(constructorArgs); + fd.field.set(obj, component); + int nComponents = fd.inType - FieldDescriptor.tCompound; + column = load(component, i, i + nComponents, + column-1, result); + i += nComponents; + } + } + } + catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + catch(InstantiationException ex) { throw new InstantiationError(); } + catch(InvocationTargetException ex) { + throw new InstantiationError("Exception was thrown by constructor"); + } + return column; + } + + protected final void bindUpdateVariables(PreparedStatement pstmt, + Object obj) + throws SQLException + { + bindUpdateVariables(pstmt, obj, 0, nFields, 0); + } + + protected final void bindQueryVariables(PreparedStatement pstmt, + Object obj) + throws SQLException + { + bindQueryVariables(pstmt, obj, 0, nFields, 0); + } + + + protected final void updateVariables(ResultSet result, Object obj) + throws SQLException + { + updateVariables(result, obj, 0, nFields, 0); + result.updateRow(); + } + + + protected final String buildQueryList(Object qbe) + { + StringBuffer buf = new StringBuffer(); + buildQueryList(buf, qbe, 0, nFields); + String condition = buf.toString(); + return condition.length() != 0 ? " where " + condition : ""; + } + + + private final int bindUpdateVariables(PreparedStatement pstmt, Object obj, + int i, int end, int column) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + Object comp = null; + if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { + if (fd.isCompound()) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + while (--nComponents >= 0) { + fd = fields[i++]; + if (!fd.isCompound()) { + pstmt.setNull(++column, + FieldDescriptor.sqlTypeMapping[fd.outType]); + } + } + } else { + pstmt.setNull(++column, + FieldDescriptor.sqlTypeMapping[fd.outType]); + } + } else { + if (!fd.bindVariable(pstmt, obj, ++column)) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + column = bindUpdateVariables(pstmt, comp, + i,i+nComponents,column-1); + i += nComponents; + } + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + + private final int bindQueryVariables(PreparedStatement pstmt, Object obj, + int i, int end, int column) + throws SQLException + { + try { + while (i < end) { + Object comp; + FieldDescriptor fd = fields[i++]; + if (!fd.field.getDeclaringClass().isInstance(obj)) { + return column; + } + int nComponents = + fd.isCompound() ? fd.outType-FieldDescriptor.tCompound : 0; + if (!fd.isBuiltin() + && fd.outType != FieldDescriptor.tClosure + && (comp = fd.field.get(obj)) != null) + { + if (!fd.bindVariable(pstmt, obj, ++column)) { + column = bindQueryVariables(pstmt, comp, + i,i+nComponents,column-1); + } + } + i += nComponents; + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + + private final void buildQueryList(StringBuffer buf, Object qbe, + int i, int end) + { + try { + while (i < end) { + Object comp; + FieldDescriptor fd = fields[i++]; + int nComponents = + fd.isCompound() ? fd.outType-FieldDescriptor.tCompound : 0; + if (!fd.isBuiltin() + && fd.outType != FieldDescriptor.tClosure + && (comp = fd.field.get(qbe)) != null) + { + if (nComponents != 0) { + buildQueryList(buf, comp, i, i+nComponents); + } else { + if (buf.length() != 0) { + buf.append(","); + } + buf.append(fd.name); + buf.append("=?"); + } + } + i += nComponents; + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + } + + protected final int updateVariables(ResultSet result, Object obj, + int i, int end, int column) + throws SQLException + { + try { + while (i < end) { + FieldDescriptor fd = fields[i++]; + Object comp = null; + if (!fd.isBuiltin() && (comp = fd.field.get(obj)) == null) { + if (fd.isCompound()) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + while (--nComponents >= 0) { + fd = fields[i++]; + if (!fd.isCompound()) { + result.updateNull(++column); + } + } + } else { + result.updateNull(++column); + } + } else { + if (!fd.updateVariable(result, obj, ++column)) { + int nComponents = fd.outType-FieldDescriptor.tCompound; + column = updateVariables(result, comp, + i, i+nComponents, column-1); + i += nComponents; + } + } + } + } catch(IllegalAccessException ex) { throw new IllegalAccessError(); } + return column; + } + +} + + + + +