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;
+ }
+
+}
+
+
+
+
+