You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@empire-db.apache.org by do...@apache.org on 2008/08/06 10:47:43 UTC
svn commit: r683173 [5/10] - in /incubator/empire-db/trunk/core/Empire-db:
./ .settings/ bin/ lib/ src/ src/META-INF/ src/org/ src/org/apache/
src/org/apache/empire/ src/org/apache/empire/commons/
src/org/apache/empire/data/ src/org/apache/empire/data/...
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBExpr.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBExpr.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBExpr.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBExpr.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,135 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+// java
+import java.util.Collection;
+import java.util.Date;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.empire.data.DataType;
+
+
+/**
+ * This abstract class is the base class for all database expression classes (e.g. DBAliasExpr or DBCalsExpr)
+ * <P>
+ *
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public abstract class DBExpr extends DBObject
+{
+ // SQL Context Flags
+ public static final long CTX_DEFAULT = 7; // Default: FullyQualified + Value
+ public static final long CTX_ALL = 15; // All Flags set (except exclusions)
+
+ public static final long CTX_NAME = 1; // Unqualified Name
+ public static final long CTX_FULLNAME = 2; // Fully Qualified Name
+ public static final long CTX_VALUE = 4; // Value Only
+ public static final long CTX_ALIAS = 8; // Rename expression
+ public static final long CTX_NOPARENTHESES = 16; // No Parentheses
+
+ // Logger
+ protected static Log log = LogFactory.getLog(DBExpr.class);
+
+ /**
+ * Used to build the SQL command. SQL for this expression must be appended to StringBuilder.
+ *
+ * @param buf the string buffer used to build the sql command
+ * @param context context flag for this SQL-Command (see CTX_??? constants).
+ */
+ public abstract void addSQL(StringBuilder buf, long context);
+
+ /**
+ * Internal function to obtain all DBColumnExpr-objects used by this expression.
+ *
+ * @param list list to which all used column expressions must be added
+ */
+ public abstract void addReferencedColumns(Set<DBColumn> list);
+
+ /**
+ * Returns the sql representation of a value.
+ *
+ * @param col the DBColumnExpr object
+ * @param value an DBExpr object, array or a basis data type(e.g. int, String)
+ * @param context the context of the DBColumnExpr object
+ * @param arraySep the separator value
+ * @return the new SQL-Command
+ */
+ protected String getObjectValue(DBColumnExpr col, Object value, long context, String arraySep)
+ {
+ // it's an Object
+ if (value instanceof DBExpr)
+ { // Wert ist Expression
+ StringBuilder buf = new StringBuilder();
+ ((DBExpr) value).addSQL(buf, context);
+ return buf.toString();
+ }
+ else if (value instanceof Collection)
+ {
+ value = ((Collection)value).toArray();
+ }
+ // Check wether it is an array
+ if (value!=null && value.getClass().isArray())
+ {
+ StringBuilder buf = new StringBuilder();
+ // An Array of Objects
+ Object[] array = (Object[]) value;
+ for (int i = 0; i < array.length; i++)
+ { // Array Separator
+ if (i > 0 && arraySep != null)
+ buf.append(arraySep);
+ // Append Value
+ buf.append(getObjectValue(col, array[i], context, arraySep));
+ }
+ return buf.toString();
+ }
+ else
+ { // Scalar Value from DB
+ DBDatabaseDriver driver = getDatabase().getDriver();
+ if (driver==null)
+ { // Convert to String
+ log.warn("No driver set for getting object value. Using default!");
+ return String.valueOf(value);
+ }
+ // Get Value Expression from Driver
+ return driver.getValueString(value, col.getDataType());
+ }
+ }
+
+ /**
+ * Returns the java class type for a given dataType
+ * @param type the data type
+ * @return return the java class used for storing values of this dataType
+ */
+ public static final Class getValueClass(DataType type)
+ {
+ switch(type)
+ {
+ case AUTOINC:
+ case INTEGER:
+ return Long.class;
+ case TEXT:
+ case CLOB:
+ return String.class;
+ case DATE:
+ case DATETIME:
+ return Date.class;
+ case CHAR:
+ return Character.class;
+ case DOUBLE:
+ return Double.class;
+ case DECIMAL:
+ return java.math.BigDecimal.class;
+ case BOOL:
+ return Boolean.class;
+ case BLOB:
+ return byte[].class;
+ default:
+ return Object.class;
+ }
+ }
+
+}
\ No newline at end of file
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBIndex.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBIndex.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBIndex.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBIndex.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,106 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+/**
+ * This class handles the primary key for the tables.
+ * The primary key contains one or more columns.
+ * <P>
+ *
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public class DBIndex extends DBObject
+{
+ // Index Types
+ public static final int STANDARD = 0;
+ public static final int UNIQUE = 1;
+ public static final int PRIMARYKEY = 2;
+
+ private String name;
+ private int type;
+ private DBColumn[] columns;
+
+ /**
+ * Constructs a DBIndex object set the specified parameters to this object.
+ *
+ * @param name the primary key name
+ * @param type the primary key type (only PRIMARYKEY)
+ * @param columns an array of one or more columns of the primary key
+ */
+ public DBIndex(String name, int type, DBColumn[] columns)
+ {
+ this.name = name;
+ this.type = type;
+ this.columns = columns;
+ }
+
+ @Override
+ public DBDatabase getDatabase()
+ {
+ return columns[0].getDatabase();
+ }
+
+ /**
+ * Returns the primary key name.
+ *
+ * @return the primary key name
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns the columns belonging to this index.
+ *
+ * @return the columns belonging to this index
+ */
+ public DBColumn[] getColumns()
+ {
+ return columns;
+ }
+
+ /**
+ * Returns the full qualified table name.
+ *
+ * @return the full qualified table name
+ */
+ public String getFullName()
+ {
+ String schema = getDatabase().getSchema();
+ return (schema!=null) ? schema+"."+name : name;
+ }
+
+ /**
+ * Returns the primary key type (only PRIMARYKEY).
+ */
+ public int getType()
+ {
+ return type;
+ }
+
+ /**
+ * Returns true if a specified DBColumn object exits in the internal DBColumn vector.
+ */
+ public boolean contains(DBColumn col)
+ {
+ for (int i = 0; i < columns.length; i++)
+ if (col.equals(columns[i]))
+ return true;
+ return false;
+ }
+
+ /**
+ * Gets the position of a specified DBColumn object.
+ */
+ public int getColumnPos(DBColumn col)
+ {
+ for (int i = 0; i < columns.length; i++)
+ if (col.equals(columns[i]))
+ return i;
+ return -1;
+ }
+
+}
+
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBJoinType.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBJoinType.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBJoinType.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBJoinType.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,27 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+/**
+ *
+ * DBJoinType contains the possibilities to join two database tables.
+ *
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public enum DBJoinType
+{
+ LEFT, // =-1,
+ INNER, // = 0,
+ RIGHT; // = 1
+
+ public static DBJoinType reversed(DBJoinType type)
+ {
+ switch(type)
+ {
+ case LEFT: return RIGHT;
+ case RIGHT: return LEFT;
+ default: return type; // no change
+ }
+ }
+}
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBObject.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBObject.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBObject.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBObject.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,65 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+// java.sql
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.empire.commons.ErrorObject;
+import org.apache.empire.commons.ErrorType;
+
+
+/**
+ * Base class for all database related objects.
+ * Every object is attached to a DBDatabase object.
+ *
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public abstract class DBObject extends ErrorObject
+{
+ // Logger
+ private static final Log log = LogFactory.getLog(DBObject.class);
+
+ /**
+ * Returns the database object to which this object belongs to.
+ * For the database object itself this function will return the this pointer.
+ *
+ * @return the database object
+ */
+ public abstract DBDatabase getDatabase();
+
+ /**
+ * Sets the current error from an SQL Exception.
+ *
+ * @param type the error type
+ * @param sqle the SQL error message
+ *
+ * @return the return value is always false
+ */
+ protected boolean error(ErrorType type, SQLException sqle)
+ {
+ log.error("Database operation failed.", sqle);
+ // converts a database error message to a human readable error message.
+ DBDatabase db = getDatabase();
+ if (db!=null && db.getDriver()!=null)
+ return error(type, db.getDriver().extractErrorMessage(sqle));
+ // Set the error Message
+ return error(type, sqle.getMessage());
+ }
+
+ /**
+ * Sets the current error from an SQL Exception.
+ *
+ * @param sqle the SQL error message
+ *
+ * @return the return value is always false
+ */
+ protected boolean error(SQLException sqle)
+ { // converts a database error message to a human readable error message.
+ return error(DBErrors.SQLException, sqle);
+ }
+
+}
\ No newline at end of file
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBQuery.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBQuery.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBQuery.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBQuery.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,538 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+import java.sql.Connection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.empire.commons.Errors;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.Options;
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.expr.compare.DBCompareColExpr;
+import org.apache.empire.db.expr.compare.DBCompareExpr;
+import org.apache.empire.db.expr.join.DBJoinExpr;
+import org.w3c.dom.Element;
+
+
+/**
+ * This class can be used to wrap a query from a DBCommand and use it like a DBRowSet.<BR>
+ * You may use this class for two purposes:
+ * <UL>
+ * <LI>In oder to define subqueries simply define a command object with the subquery and wrap it inside a DBQuery.
+ * Then in a second command object you can reference this Query to join with your other tables and views.
+ * In order to join other columns with your query use findQueryColumn(DBColumnExpr expr) to get the
+ * query column object for a given column expression in the orignial select clause.</LI>
+ * <LI>With a key supplied you can have an updateable query that will update several records at once.</LI>
+ * </UL>
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public class DBQuery extends DBRowSet
+{
+ public static class DBQueryColumn extends DBColumn
+ {
+ protected DBColumnExpr expr;
+
+ /**
+ * Constructs a DBQueryColumn object set the specified parameters to this object.
+ * <P>
+ * @param query the DBQuery object
+ * @param expr the concrete DBColumnExpr object
+ */
+ public DBQueryColumn(DBQuery query, DBColumnExpr expr)
+ { // call base
+ super(query, expr.getName());
+ // set Expression
+ this.expr = expr;
+ }
+
+ @Override
+ public DataType getDataType()
+ {
+ return expr.getDataType();
+ }
+
+ @Override
+ public double getSize()
+ {
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return 0.0;
+ return column.getSize();
+ }
+
+ @Override
+ public boolean isReadOnly()
+ {
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return true;
+ return column.isReadOnly();
+ }
+
+ @Override
+ public boolean isRequired()
+ {
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return false;
+ return column.isRequired();
+ }
+
+ @Override
+ public boolean checkValue(Object value)
+ {
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return true;
+ return column.checkValue(value);
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ if (attributes != null && attributes.containsKey(name))
+ return attributes.get(name);
+ // Otherwise ask expression
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return null;
+ return column.getAttribute(name);
+ }
+
+ @Override
+ public Options getOptions()
+ {
+ if (options != null)
+ return options;
+ // Otherwise ask expression
+ DBColumn column = expr.getUpdateColumn();
+ if (column==null)
+ return null;
+ return column.getOptions();
+ }
+
+ @Override
+ public Element addXml(Element parent, long flags)
+ {
+ return expr.addXml(parent, flags);
+ }
+ }
+
+ private static int queryCount = 1;
+
+ protected DBCommand cmd;
+ protected DBColumn[] keyColumns = null;
+ protected DBQueryColumn[] queryColumns = null;
+ protected String alias;
+
+ /**
+ * Constructor initializes the query object.
+ * Saves the columns and the primary keys of this query.
+ *
+ * @param cmd the SQL-Command
+ * @param keyColumns an array of the primary key columns
+ */
+ public DBQuery(DBCommand cmd, DBColumn[] keyColumns)
+ { // Set the column expressions
+ super(cmd.getDatabase());
+ this.cmd = cmd;
+ // Set Query Columns
+ DBColumnExpr[] exprList = cmd.getSelectExprList();
+ queryColumns = new DBQueryColumn[exprList.length];
+ for (int i = 0; i < exprList.length; i++)
+ { // Init Columns
+ columns.add(exprList[i].getUpdateColumn());
+ queryColumns[i] = new DBQueryColumn(this, exprList[i]);
+ }
+ // Set the key Column
+ this.keyColumns = keyColumns;
+ // set alias
+ this.alias = "q" + String.valueOf(queryCount);
+ queryCount++;
+ }
+
+ /**
+ * Constructs a new DBQuery object initialize the query object.
+ * Save the columns and the primary key of this query.
+ *
+ * @param cmd the SQL-Command
+ * @param keyColumn the primary key column
+ */
+ public DBQuery(DBCommand cmd, DBColumn keyColumn)
+ { // Set the column expressions
+ this(cmd, new DBColumn[] { keyColumn });
+ }
+
+ /**
+ * Creaes a DBQuery object from a given command object.
+ *
+ * @param cmd the command object representing an SQL-Command.
+ */
+ public DBQuery(DBCommand cmd)
+ { // Set the column expressions
+ this(cmd, (DBColumn[]) null);
+ }
+
+ /**
+ * not applicable - returns null
+ */
+ @Override
+ public String getName()
+ {
+ return null;
+ }
+
+ /**
+ * not applicable - returns null
+ */
+ @Override
+ public String getAlias()
+ {
+ return alias;
+ }
+
+ /**
+ * Gets all columns of this rowset (e.g. for cmd.select()).
+ *
+ * @return all columns of this rowset
+ */
+ public DBQueryColumn[] getQueryColumns()
+ {
+ return queryColumns;
+ }
+
+ /**
+ * This function searchs for equal columns given by the
+ * specified DBColumnExpr object.
+ *
+ * @param expr the DBColumnExpr object
+ * @return the located column (only DBViewColumn onjects)
+ */
+ public DBQueryColumn findQueryColumn(DBColumnExpr expr)
+ {
+ for (int i = 0; i < queryColumns.length; i++)
+ {
+ if (queryColumns[i].expr.equals(expr))
+ return queryColumns[i];
+ }
+ // not found
+ return null;
+ }
+
+ /**
+ * return query key columns
+ */
+ @Override
+ public DBColumn[] getKeyColumns()
+ {
+ return keyColumns;
+ }
+
+ /**
+ * Returns a array of primary key columns by a specified DBRecord object.
+ *
+ * @param record the DBRecord object, contains all fields and the field properties
+ * @return a array of primary key columns
+ */
+ @Override
+ public Object[] getRecordKey(DBRecord record)
+ {
+ if (record == null || record.getRowSet() != this)
+ {
+ error(Errors.InvalidArg, record, "record");
+ return null; // Invalid Argument
+ }
+ // get Key
+ return (Object[]) record.getRowSetData();
+ }
+
+ /**
+ * Adds the select SQL Command of this object to the specified StringBuilder object.
+ *
+ * @param buf the SQL-Command
+ * @param context the current SQL-Command context
+ */
+ @Override
+ public void addSQL(StringBuilder buf, long context)
+ {
+ buf.append("(");
+ buf.append(cmd.getSelect());
+ buf.append(")");
+ // Add Alias
+ if ((context & CTX_ALIAS) != 0 && alias != null)
+ { // append alias
+ buf.append(" ");
+ buf.append(alias);
+ }
+ }
+
+ /**
+ * Initialize specified DBRecord object with primary key
+ * columns (the Object[] keyValues).
+ *
+ * @param rec the Record object
+ * @param keyValues an array of the primary key columns
+ * @return true if successful
+ */
+ @Override
+ public boolean initRecord(DBRecord rec, Object[] keyValues)
+ {
+ // Inititialisierung
+ if (!prepareInitRecord(rec, DBRecord.REC_EMTPY, keyValues))
+ return false;
+ // Initialize all Fields
+ Object[] fields = rec.getFields();
+ for (int i = 0; i < fields.length; i++)
+ fields[i] = ObjectUtils.NO_VALUE;
+ // Set primary key values
+ if (keyValues != null)
+ { // search for primary key fields
+ DBColumn[] keyColumns = getKeyColumns();
+ for (int i = 0; i < keyColumns.length; i++)
+ if (columns.contains(keyColumns[i]))
+ fields[columns.indexOf(keyColumns[i])] = keyValues[i];
+ }
+ // Init
+ return completeInitRecord(rec);
+ }
+
+ /**
+ * Returns an error, because querys could't add new records to the database.
+ *
+ * @param rec the DBRecord object, contains all fields and the field properties
+ * @param conn a valid database connection
+ * @return an error, because querys could't add new records to the database
+ */
+ @Override
+ public boolean createRecord(DBRecord rec, Connection conn)
+ {
+ return error(Errors.NotImplemented, "addRecord");
+ }
+
+ /**
+ * Creates a select SQL-Command of the query call the InitRecord method to execute the SQL-Command.
+ *
+ * @param rec rec the DBRecord object, contains all fields and the field properties
+ * @param key an array of the primary key columns
+ * @param conn a valid connection to the database.
+ * @return true if successful
+ */
+ @Override
+ public boolean readRecord(DBRecord rec, Object[] key, Connection conn)
+ {
+ if (conn == null || rec == null)
+ return error(Errors.InvalidArg, null, "conn|rec");
+ DBColumn[] keyColumns = getKeyColumns();
+ if (key == null || keyColumns.length != key.length)
+ return error(DBErrors.RecordInvalidKey, key);
+ // Select
+ for (int i = 0; i < keyColumns.length; i++)
+ cmd.where(keyColumns[i].is(key[i]));
+ // Read Record
+ if (!readRecord(rec, cmd, conn))
+ { // Record not found
+ if (getErrorType() == DBErrors.QueryNoResult)
+ return error(DBErrors.RecordNotFound, key);
+ // Return given error
+ return false;
+ }
+ // Set RowSetData
+ rec.changeState(DBRecord.REC_VALID, key.clone());
+ return success();
+ }
+
+ /**
+ * Updates a query record by creating individual update commands for each table.
+ *
+ * @param rec the DBRecord object. contains all fields and the field properties
+ * @param conn a valid connection to the database.
+ * @return true if succesfull
+ */
+ @Override
+ public boolean updateRecord(DBRecord rec, Connection conn)
+ {
+ if (conn == null || rec == null)
+ return error(Errors.InvalidArg, null, "conn|rec");
+ // Has record been modified?
+ if (rec.isModified() == false)
+ return success(); // Nothing to update
+ // Must have key Columns
+ DBColumn[] keyColumns = getKeyColumns();
+ if (keyColumns==null)
+ return error(DBErrors.NoPrimaryKey, getAlias());
+ // Get the fields and the flags
+ Object[] fields = rec.getFields();
+ // Get all Update Commands
+ Map<DBRowSet, DBCommand> updCmds = new HashMap<DBRowSet, DBCommand>(3);
+ for (int i = 0; i < columns.size(); i++)
+ { // get the table
+ DBColumn col = columns.get(i);
+ if (col == null)
+ continue;
+ DBRowSet table = col.getRowSet();
+ DBCommand updCmd = updCmds.get(table);
+ if (updCmd == null)
+ { // Add a new Command
+ updCmd = db.createCommand();
+ updCmds.put(table, updCmd);
+ }
+ /*
+ * if (updateTimestampColumns.contains( col ) ) { // Check the update timestamp cmd.set( col.to( DBDatabase.SYSDATE ) ); }
+ */
+ // Set the field Value
+ boolean modified = rec.wasModified(i);
+ if (modified == true)
+ { // Update a field
+ if (col.isReadOnly() && log.isDebugEnabled())
+ log.debug("updateRecord: Read-only column '" + col.getName() + " has been modified!");
+ // Check the value
+ if (!col.checkValue(fields[i]))
+ return error(col);
+ // Set
+ updCmd.set(col.to(fields[i]));
+ }
+ }
+ // the commands
+ Object[] keys = (Object[]) rec.getRowSetData();
+ Iterator<DBRowSet> tables = updCmds.keySet().iterator();
+ while (tables.hasNext())
+ {
+ int i = 0;
+ // Iterate through options
+ DBRowSet table = tables.next();
+ DBCommand upd = updCmds.get(table);
+ // Is there something to update
+ if (upd.set == null)
+ continue; // nothing to do for this table!
+ // Evaluate Joins
+ for (i = 0; cmd.joins != null && i < cmd.joins.size(); i++)
+ {
+ DBJoinExpr join = cmd.joins.get(i);
+ DBColumn left = join.getLeft() .getUpdateColumn();
+ DBColumn right = join.getRight().getUpdateColumn();
+ if (left.getRowSet()==table && table.isKeyColumn(left))
+ if (!addJoinRestriction(upd, left, right, keyColumns, rec))
+ return error(Errors.ItemNotFound, left.getFullName());
+ if (right.getRowSet()==table && table.isKeyColumn(right))
+ if (!addJoinRestriction(upd, right, left, keyColumns, rec))
+ return error(Errors.ItemNotFound, right.getFullName());
+ }
+ // Evaluate Existing restrictions
+ for (i = 0; cmd.where != null && i < cmd.where.size(); i++)
+ {
+ DBCompareExpr cmp = cmd.where.get(i);
+ if (cmp instanceof DBCompareColExpr)
+ { // Check whether
+ DBCompareColExpr cmpExpr = (DBCompareColExpr) cmp;
+ DBColumn col = cmpExpr.getColumnExpr().getUpdateColumn();
+ if (col.getRowSet() == table)
+ upd.where(cmp);
+ }
+ else
+ { // other constraints are not supported
+ return error(Errors.NotSupported, "updateRecord");
+ }
+ }
+ // Add Restrictions
+ for (i = 0; i < keyColumns.length; i++)
+ if (keyColumns[i].getRowSet() == table)
+ upd.where(keyColumns[i].is(keys[i]));
+
+ // Set Update Timestamp
+ int timestampIndex = -1;
+ Object timestampValue = null;
+ if (table.getTimestampColumn() != null)
+ {
+ DBColumn tsColumn = table.getTimestampColumn();
+ timestampIndex = this.getColumnIndex(tsColumn);
+ if (timestampIndex>=0)
+ { // The timestamp is availabe in the record
+ timestampValue = db.getUpdateTimestamp(conn);
+ Object lastTS = fields[timestampIndex];
+ if (ObjectUtils.isEmpty(lastTS)==false)
+ upd.where(tsColumn.is(lastTS));
+ // Set new Timestamp
+ upd.set(tsColumn.to(timestampValue));
+ }
+ else
+ { // Timestamp columns has not been provided with the record
+ upd.set(tsColumn.to(DBDatabase.SYSDATE));
+ }
+ }
+
+ // Execute SQL
+ int affected = db.executeSQL(upd.getUpdate(), upd.getCmdParams(), conn);
+ if (affected <= 0)
+ { // Error
+ if (affected == 0)
+ { // Record not found
+ error(DBErrors.RecordUpdateFailed, table.getName());
+ }
+ // Rollback
+ db.rollback(conn);
+ return false;
+ }
+ else if (affected > 1)
+ { // More than one record
+ error(DBErrors.RecordUpdateInvalid, table.getName());
+ }
+ else
+ { // success
+ log.info("Record for table '" + table.getName() + " sucessfully updated!");
+ }
+ // Correct Timestamp
+ if (timestampIndex >= 0)
+ { // Set the correct Timestamp
+ fields[timestampIndex] = timestampValue;
+ }
+ }
+ // success
+ rec.changeState(DBRecord.REC_VALID, keys);
+ return success();
+ }
+
+ /**
+ * Adds join restrictions to the supplied command object.
+ */
+ private boolean addJoinRestriction(DBCommand upd, DBColumn updCol, DBColumn keyCol, DBColumn[] keyColumns, DBRecord rec)
+ { // Find key for forein field
+ Object rowsetData = rec.getRowSetData();
+ for (int i = 0; i < keyColumns.length; i++)
+ if (keyColumns[i]==keyCol && rowsetData!=null)
+ { // Set Field from Key
+ upd.where(updCol.is(((Object[]) rowsetData)[i]));
+ return true;
+ }
+ // Not found, what about the reocrd
+ int index = this.getColumnIndex(updCol);
+ if (index<0)
+ index = this.getColumnIndex(keyCol);
+ if (index>=0)
+ { // Field Found
+ if (rec.wasModified(index))
+ return false; // Ooops, Key field has changed
+ // Set Constraint
+ upd.where(updCol.is(rec.getValue(index)));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Deletes a record identified by its primary key from the database.
+ *
+ * @param keys array of primary key values
+ * @param conn a valid database connection
+ * @return true if the record has been successfully deleted or false otherwise
+ */
+ @Override
+ public boolean deleteRecord(Object[] keys, Connection conn)
+ {
+ return error(Errors.NotImplemented, "deleteRecord");
+ }
+
+}
\ No newline at end of file
Added: incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBReader.java
URL: http://svn.apache.org/viewvc/incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBReader.java?rev=683173&view=auto
==============================================================================
--- incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBReader.java (added)
+++ incubator/empire-db/trunk/core/Empire-db/src/org/apache/empire/db/DBReader.java Wed Aug 6 01:47:37 2008
@@ -0,0 +1,903 @@
+/*
+ * ESTEAM Software GmbH
+ */
+package org.apache.empire.db;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.beanutils.ConstructorUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.empire.commons.Errors;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.data.ColumnExpr;
+import org.apache.empire.data.DataType;
+import org.apache.empire.xml.XMLUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+
+/**
+ * <P>
+ * This class is used to perform database queries from a DBCommand object and access the results.<BR>
+ * In oder to perform a query call the open() function or - for single row queries - call getRecordData();<BR>
+ * You can iterate through the rows using moveNext() or an iterator.<BR>
+ * <P>
+ * However take care: A reader must always be explcitly closed using the close() method!<BR>
+ * Otherwise you may lock the JDBC connection and run out of resources.<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * To access and work with the query result you can do one of the following:<BR>
+ * <ul>
+ * <li>access field values directly by using one of the get... functions (see {@link DBRecordData})</li>
+ * <li>get the rows as a list of Java Beans using by using {@link DBReader#getBeanList(Class, int)}</li>
+ * <li>get the rows as an XML-Document using {@link DBReader#getXmlDocument()} </li>
+ * <li>initialize a DBRecord with the current row data using {@link DBReader#initRecord(DBRowSet, DBRecord)}<br/>
+ * This will allow you to modify and update the data.
+ * </li>
+ * </ul>
+ *
+ * @author ESTEAM software <A TARGET="esteam" HREF="http://www.esteam.de">www.esteam.de </A>
+ */
+public class DBReader extends DBRecordData
+{
+ public abstract class DBReaderIterator implements Iterator<DBRecordData>
+ {
+ protected int curCount = 0;
+ protected int maxCount = 0;
+
+ public DBReaderIterator(int maxCount)
+ {
+ if (maxCount < 0)
+ maxCount = 0x7FFFFFFF; // Highest positive number
+ // Set Maxcount
+ this.maxCount = maxCount;
+ }
+
+ /**
+ * Implements the Iterator Interface Method remove not implemented and not applicable.
+ */
+ public void remove()
+ {
+ log.error("DBReader.remove ist not implemented!");
+ }
+
+ /**
+ * Disposes the iterator.
+ */
+ public void dispose()
+ {
+ curCount = maxCount = -1;
+ }
+ }
+
+ /**
+ * This is an iterator for scrolling resultsets.
+ * This iterator has no such limitations as the forward iterator.
+ */
+ public class DBReaderScrollableIterator extends DBReaderIterator
+ {
+ public DBReaderScrollableIterator(int maxCount)
+ {
+ super(maxCount);
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return true if there is another record to read
+ */
+ public boolean hasNext()
+ {
+ try
+ { // clear previous error
+ clearError();
+ // Check position
+ if (curCount >= maxCount)
+ return false;
+ // Check Recordset
+ if (rset == null || rset.isLast() || rset.isAfterLast())
+ return false;
+ // there are more records
+ return true;
+ } catch (SQLException e)
+ {
+ return error(e);
+ }
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return the current Record interface
+ */
+ public DBRecordData next()
+ {
+ if ((curCount < maxCount && moveNext()))
+ {
+ curCount++;
+ return DBReader.this;
+ }
+ // Past the end!
+ return null;
+ }
+ }
+
+ /**
+ * This is an iterator for forward only resultsets.
+ * There is an important limitation on this iterator: After calling
+ * hasNext() the caller may not use any functions on the current item any more. i.e.
+ * Example:
+ * while (i.hasNext())
+ * {
+ * DBRecordData r = i.next();
+ * Object o = r.getValue(0); // ok
+ *
+ * bool last = i.hasNext(); // ok
+ * Object o = r.getValue(0); // Illegal call!
+ * }
+ */
+ public class DBReaderForwardIterator extends DBReaderIterator
+ {
+ private boolean getCurrent = true;
+ private boolean hasCurrent = false;
+
+ public DBReaderForwardIterator(int maxCount)
+ {
+ super(maxCount);
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return true if there is another record to read
+ */
+ public boolean hasNext()
+ {
+ // Check position
+ if (curCount >= maxCount)
+ return false;
+ if (rset == null)
+ return error(Errors.ObjectNotValid, getClass().getName());
+ // Check next Record
+ if (getCurrent == true)
+ {
+ getCurrent = false;
+ hasCurrent = moveNext();
+ }
+ return hasCurrent;
+ }
+
+ /**
+ * Implements the Iterator Interface.
+ *
+ * @return the current Record interface
+ */
+ public DBRecordData next()
+ {
+ if (hasCurrent == false)
+ return null; // Past the end!
+ // next called without call to hasNext ?
+ if (getCurrent && !moveNext())
+ { // No more records
+ hasCurrent = false;
+ getCurrent = false;
+ return null;
+ }
+ // Move forward
+ curCount++;
+ getCurrent = true;
+ return DBReader.this;
+ }
+ }
+
+ // Logger
+ @SuppressWarnings("hiding")
+ protected static Log log = LogFactory.getLog(DBReader.class);
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed
+ * @author bond
+ */
+ private static ThreadLocal<Map<DBReader, Exception>> threadLocalOpenResultSets = new ThreadLocal<Map<DBReader, Exception>>();
+
+ // Object references
+ private DBDatabase db = null;
+ private DBColumnExpr[] colList = null;
+
+ // Direct column access
+ protected ResultSet rset = null;
+
+ /**
+ * Constructs an empty DBRecordSet object.
+ */
+ public DBReader()
+ {
+ // Default Constructor
+ }
+
+ /**
+ * Returns the current DBDatabase object.
+ *
+ * @return the current DBDatabase object
+ */
+ @Override
+ public DBDatabase getDatabase()
+ {
+ return db;
+ }
+
+ public boolean getScrollable()
+ {
+ try
+ {
+ // Check Resultset
+ return (rset!=null && rset.getType()!=ResultSet.TYPE_FORWARD_ONLY);
+ } catch (SQLException e)
+ {
+ log.error("Cannot determine Resultset type", e);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the index value by a specified DBColumnExpr object.
+ *
+ * @return the index value
+ */
+ @Override
+ public int getFieldIndex(ColumnExpr column)
+ {
+ if (colList != null)
+ {
+ // First chance: Try to find an exact match
+ for (int i = 0; i < colList.length; i++)
+ {
+ if (colList[i].equals(column))
+ return i;
+ }
+ // Second chance: Try Update Column
+ if (column instanceof DBColumn)
+ {
+ for (int i = 0; i < colList.length; i++)
+ {
+ DBColumn updColumn = colList[i].getUpdateColumn();
+ if (updColumn!=null && updColumn.equals(column))
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /** Get the column Expression at position */
+ @Override
+ public DBColumnExpr getColumnExpr(int iColumn)
+ {
+ if (colList == null || iColumn < 0 || iColumn >= colList.length)
+ return null; // Index out of range
+ // return column Expression
+ return colList[iColumn];
+ }
+
+ /**
+ * Returns the index value by a specified column name.
+ *
+ * @param column the column name
+ * @return the index value
+ */
+ @Override
+ public int getFieldIndex(String column)
+ {
+ if (colList != null)
+ {
+ for (int i = 0; i < colList.length; i++)
+ if (colList[i].getName().equalsIgnoreCase(column))
+ return i;
+ }
+ // not found
+ return -1;
+ }
+
+ /**
+ * Checks wehter a column value is null Unlike the base
+ * class implementation, this class directly check the value fromt the
+ * resultset.
+ *
+ * @param index index of the column
+ * @return true if the value is null or false otherwise
+ */
+ @Override
+ public boolean isNull(int index)
+ {
+ if (index < 0 || index >= colList.length)
+ { // Index out of range
+ log.error("Index out of range: " + index);
+ return true;
+ }
+ try
+ { // Check Value on Resultset
+ clearError();
+ rset.getObject(index + 1);
+ return rset.wasNull();
+ } catch (Exception e)
+ {
+ log.error("isNullValue exception", e);
+ return super.isNull(index);
+ }
+ }
+
+ /**
+ * Returns a data value identified by the column index.
+ *
+ * @param index index of the column
+ * @return the value
+ */
+ @Override
+ public Object getValue(int index)
+ {
+ if (index < 0 || index >= colList.length)
+ { // Index out of range
+ log.error("Index out of range: " + index);
+ return null;
+ }
+ try
+ { // Get Value from Resultset
+ DataType dataType = colList[index].getDataType();
+ return db.driver.getResultValue(rset, index + 1, dataType);
+
+ } catch (Exception e)
+ {
+ log.error("getValue exception", e);
+ return null;
+ }
+ }
+
+ /** returns null true if the Rowset is not null, and otherwise false */
+ public boolean isOpen()
+ {
+ return (rset != null);
+ }
+
+ /**
+ * Opens the reader by executing the given SQL command.<BR>
+ * After the reader is open, the reader's position is before the first record.<BR>
+ * Use moveNext or iterator() to step through the rows.<BR>
+ * Data of the current row can be accessed through the functions on the RecordData interface.<BR>
+ * <P>
+ * ATTENTION: After using the reader it must be closed using the close() method!<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param scrollable true if the reader should be scrollable or false if not
+ * @param conn a valid JDBC connection.
+ * @return true if successful
+ */
+ public boolean open(DBCommandExpr cmd, boolean scrollable, Connection conn)
+ {
+ if (isOpen())
+ close();
+ // SQL Commanand
+ String sqlCmd = cmd.getSelect();
+ // Create Statement and connection
+ db = cmd.getDatabase();
+ rset = db.executeQuery(sqlCmd, null, scrollable, conn);
+ if (rset==null)
+ return error(db);
+ // sucessfully opened
+ colList = cmd.getSelectExprList();
+ addOpenResultSet();
+ return success();
+ }
+
+ /**
+ * Opens the reader by executing the given SQL command.<BR>
+ * <P>
+ * see {@link DBReader#open(DBCommandExpr, boolean, Connection)}
+ * </P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param conn a valid JDBC connection.
+ * @return true if successful
+ */
+ public boolean open(DBCommandExpr cmd, Connection conn)
+ {
+ return open(cmd, false, conn);
+ }
+
+ /**
+ * <P>
+ * Opens the reader by executing the given SQL command and moves to the first row.<BR>
+ * If true is returned data of the row can be accessed through the functions on the RecordData interface.<BR>
+ * This function is intended for single row queries and provided for convenience.<BR>
+ * However it behaves exacly as calling reader.open() and reader.moveNext()<BR>
+ * <P>
+ * ATTENTION: After using the reader it must be closed using the close() method!<BR>
+ * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR>
+ * <P>
+ * @param cmd the SQL-Command with cmd.getSelect()
+ * @param conn a valid JDBC connection.
+ * @return true if successful
+ */
+ public boolean getRecordData(DBCommandExpr cmd, Connection conn)
+ { // Open the record
+ if (!open(cmd, conn))
+ return false;
+ // Get First Record
+ if (!moveNext())
+ { // Close
+ return error(DBErrors.QueryNoResult, cmd.getSelect());
+ }
+ return success();
+ }
+
+ /**
+ * Closes the DBRecordSet object, the Statement object and detach the columns.<BR>
+ * A reader must always be closed immediately after using it.
+ */
+ @Override
+ public void close()
+ {
+ try
+ { // Dispose iterator
+ if (iterator != null)
+ {
+ iterator.dispose();
+ iterator = null;
+ }
+ // Close Recordset
+ if (rset != null)
+ {
+ getDatabase().closeResultSet(rset);
+ removeOpenResultSet();
+ }
+ // Detach columns
+ colList = null;
+ rset = null;
+ // Done
+ } catch (Exception e)
+ { // What's wrong here?
+ log.warn(e.toString());
+ }
+ }
+
+ /**
+ * Moves the cursor down the given number of rows.
+ *
+ * @return true if the reader is on a valid record or false otherwise
+ */
+ public boolean skipRows(int count)
+ {
+ try
+ { // clear previous error
+ clearError();
+ // Check Recordset
+ if (rset == null)
+ return error(Errors.ObjectNotValid, getClass().getName());
+ // Forward only cursor?
+ int type = rset.getType();
+ if (type == ResultSet.TYPE_FORWARD_ONLY)
+ {
+ if (count < 0)
+ return error(Errors.InvalidArg, count, "count");
+ // Move
+ for (; count > 0; count--)
+ {
+ if (!moveNext())
+ return false;
+ }
+ return true;
+ }
+ // Scrollable Cursor
+ if (count > 0)
+ { // Move a single record first
+ if (rset.next() == false)
+ return false;
+ // Move relative
+ if (count > 1)
+ return rset.relative(count - 1);
+ }
+ else if (count < 0)
+ { // Move a single record first
+ if (rset.previous() == false)
+ return false;
+ // Move relative
+ if (count < -1)
+ return rset.relative(count + 1);
+ }
+ return true;
+
+ } catch (SQLException e)
+ { // an error ocurred
+ return error(e);
+ }
+ }
+
+ /**
+ * Moves the cursor down one row from its current position.
+ *
+ * @return true if the reader is on a valid record or false otherwise
+ */
+ public boolean moveNext()
+ {
+ try
+ { // clear previous error
+ clearError();
+ // Check Recordset
+ if (rset == null)
+ return error(Errors.ObjectNotValid, getClass().getName());
+ // Move Next
+ if (rset.next() == false)
+ { // Close recordset automatically after last record
+ close();
+ clearError();
+ return false;
+ }
+ return true;
+
+ } catch (SQLException e)
+ { // an error ocurred
+ return error(e);
+ }
+ }
+
+ private DBReaderIterator iterator = null; // there can only be one!
+
+ /**
+ * Returns an row iterator for this reader.<BR>
+ * There can only be one iterator at a time.
+ * <P>
+ * @param maxCount the maximum number of item that shold be returned by this iterator
+ * @return the row interator
+ */
+ public Iterator<DBRecordData> iterator(int maxCount)
+ {
+ if (iterator == null && rset != null)
+ {
+ if (getScrollable())
+ iterator = new DBReaderScrollableIterator(maxCount);
+ else
+ iterator = new DBReaderForwardIterator(maxCount);
+ }
+ return iterator;
+ }
+
+ /**
+ * <PRE>
+ * Returns an row iterator for this reader.
+ * There can only be one iterator at a time.
+ * </PRE>
+ * @return the row interator
+ */
+ public final Iterator<DBRecordData> iterator()
+ {
+ return iterator(-1);
+ }
+
+ /**
+ * <PRE>
+ * initializes a DBRecord object with the values of the current row.
+ * At least all primary key columns of the target rowset must be provided by this reader.
+ * This function is equivalent to calling rowset.initRecord(rec, reader)
+ * set also {@link DBRowSet#initRecord(DBRecord, DBRecordData)});
+ * </PRE>
+ * @param rowset the rowset to which to attach
+ * @param rec the record which to initialize
+ * @return true if the record has been initialized sucessfully or false otherwise
+ */
+ public boolean initRecord(DBRowSet rowset, DBRecord rec)
+ {
+ if (rowset==null)
+ return error(Errors.InvalidArg, rowset, "rowset");
+ if (rowset.initRecord(rec, this)==false)
+ return error(rowset);
+ return success();
+ }
+
+ /**
+ * Returns the result of a query as a list of objects resticted
+ * to a maximum number of objects (unless maxCount is -1).
+ */
+ @SuppressWarnings("unchecked")
+ public <T> ArrayList<T> getBeanList(Class<T> c, int maxCount)
+ {
+ // Check Recordset
+ if (rset == null)
+ { // Resultset not available
+ error(Errors.ObjectNotValid, getClass().getName());
+ return null;
+ }
+ // Query List
+ try
+ {
+ // Check whether we can use a constructor
+ Class[] paramTypes = new Class[getFieldCount()];
+ for (int i = 0; i < colList.length; i++)
+ paramTypes[i] = DBExpr.getValueClass(colList[i].getDataType());
+ // Find Constructor
+ Constructor ctor = findMatchingAccessibleConstructor(c, paramTypes);
+ Object[] args = (ctor!=null) ? new Object[getFieldCount()] : null;
+
+ // Create a list of beans
+ ArrayList<T> list = new ArrayList<T>();
+ while (moveNext() && maxCount != 0)
+ { // Create bean an init
+ if (ctor!=null)
+ { // Use Constructor
+ for (int i = 0; i < getFieldCount(); i++)
+ args[i] = getValue(i);
+ T bean = (T)ctor.newInstance(args);
+ list.add(bean);
+ }
+ else
+ { // Use Property Setters
+ T bean = c.newInstance();
+ if (getBeanProperties(bean)==false)
+ return null;
+ list.add(bean);
+ }
+ // Decrease count
+ if (maxCount > 0)
+ maxCount--;
+ }
+ // done
+ return list;
+ } catch (InvocationTargetException e)
+ {
+ error(e);
+ return null;
+ } catch (IllegalAccessException e)
+ {
+ error(e);
+ return null;
+ } catch (InstantiationException e)
+ {
+ error(e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the result of a query as a list of objects.
+ */
+ public <T> ArrayList<T> getBeanList(Class<T> c)
+ {
+ return getBeanList(c, -1);
+ }
+
+ /**
+ * Moves the cursor down one row from its current position.
+ *
+ * @return true if successful
+ */
+ @Override
+ public boolean addColumnDesc(Element parent)
+ {
+ if (colList == null)
+ return error(Errors.ObjectNotValid, getClass().getName());
+ // Add Field Description
+ for (int i = 0; i < colList.length; i++)
+ colList[i].addXml(parent, 0);
+ return success();
+ }
+
+ /**
+ * Adds all children to a parent.
+ *
+ * @param parent the parent element below which to search the child
+ * @return true if successful
+ */
+ @Override
+ public boolean addRowValues(Element parent)
+ {
+ if (rset == null)
+ return error(Errors.ObjectNotValid, getClass().getName());
+ // Add all children
+ for (int i = 0; i < colList.length; i++)
+ { // Read all
+ String name = colList[i].getName();
+ String idColumnAttr = getXmlDictionary().getRowIdColumnAttribute();
+ if (name.equalsIgnoreCase("id"))
+ { // Add Attribute
+ parent.setAttribute(idColumnAttr, getString(i));
+ }
+ else
+ { // Add Element
+ String value = getString(i);
+ Element elem = XMLUtil.addElement(parent, name, value);
+ if (value == null)
+ elem.setAttribute("null", "yes"); // Null-Value
+ }
+ }
+ return success();
+ }
+
+ /**
+ * Adds all children to a parent.
+ *
+ * @param parent the parent element below which to search the child
+ * @return true if successful
+ */
+ public int addRows(Element parent)
+ {
+ int count = 0;
+ if (rset == null)
+ return 0;
+ // Add all rows
+ String rowElementName = getXmlDictionary().getRowElementName();
+ while (moveNext())
+ {
+ addRowValues(XMLUtil.addElement(parent, rowElementName));
+ count++;
+ }
+ return count;
+ }
+
+ /**
+ * returns the DBXmlDictionary that should used to generate XMLDocuments<BR>
+ * @return the DBXmlDictionary
+ */
+ protected DBXmlDictionary getXmlDictionary()
+ {
+ return DBXmlDictionary.getInstance();
+ }
+
+ /**
+ * Returns a XML document with the field descriptiona an values of this record.
+ *
+ * @return the new XML Document object
+ */
+ @Override
+ public Document getXmlDocument()
+ {
+ if (rset == null)
+ return null;
+ // Create Document
+ String rowsetElementName = getXmlDictionary().getRowSetElementName();
+ Element root = XMLUtil.createDocument(rowsetElementName);
+ // Add Field Description
+ if (!addColumnDesc(root))
+ return null;
+ // Add row rset
+ addRows(root);
+ // return Document
+ return root.getOwnerDocument();
+ }
+
+ /** returns the number of the elements of the colList array */
+ @Override
+ public int getFieldCount()
+ {
+ return (colList != null) ? colList.length : 0;
+ }
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed.
+ *
+ * @author bond
+ */
+ private synchronized void addOpenResultSet()
+ {
+ // add this to the vector of open resultsets on this thread
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets == null)
+ {
+ // Lazy initialization of the
+ openResultSets = new HashMap<DBReader, Exception>(2);
+ threadLocalOpenResultSets.set(openResultSets);
+ }
+
+ Exception stackException = openResultSets.get(this);
+ if (stackException != null)
+ {
+ log
+ .error(
+ "DBRecordSet.addOpenResultSet called for an object which is already in the open list. This is the stack of the method opening the object which was not previously closed.",
+ stackException);
+ // the code continues and overwrites the logged object with the new one
+ }
+ // get the current stack trace
+ openResultSets.put(this, new Exception());
+ }
+
+ /**
+ * Support for finding code errors where a DBRecordSet is opened but not closed.
+ *
+ * @author bond
+ */
+ private synchronized void removeOpenResultSet()
+ {
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets.containsKey(this) == false)
+ {
+ log
+ .error(
+ "DBRecordSet.removeOpenResultSet called for an object which is not in the open list. Here is the current stack.",
+ new Exception());
+ }
+ else
+ {
+ openResultSets.remove(this);
+ }
+ }
+
+ /**
+ * copied from org.apache.commons.beanutils.ConstructorUtils since it's private there
+ */
+ private static Constructor findMatchingAccessibleConstructor(Class clazz, Class[] parameterTypes)
+ {
+ // See if we can find the method directly
+ // probably faster if it works
+ // (I am not sure whether it's a good idea to run into Exceptions)
+ // try {
+ // Constructor ctor = clazz.getConstructor(parameterTypes);
+ // try {
+ // // see comment in org.apache.commons.beanutils.ConstructorUtils
+ // ctor.setAccessible(true);
+ // } catch (SecurityException se) { /* ignore */ }
+ // return ctor;
+ // } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all constructors
+ int paramSize = parameterTypes.length;
+ Constructor[] ctors = clazz.getConstructors();
+ for (int i = 0, size = ctors.length; i < size; i++)
+ { // compare parameters
+ Class[] ctorParams = ctors[i].getParameterTypes();
+ int ctorParamSize = ctorParams.length;
+ if (ctorParamSize == paramSize)
+ { // Param Size matches
+ boolean match = true;
+ for (int n = 0; n < ctorParamSize; n++)
+ {
+ if (!ObjectUtils.isAssignmentCompatible(ctorParams[n], parameterTypes[n]))
+ {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ // get accessible version of method
+ Constructor ctor = ConstructorUtils.getAccessibleConstructor(ctors[i]);
+ if (ctor != null) {
+ try {
+ ctor.setAccessible(true);
+ } catch (SecurityException se) { /* ignore */ }
+ return ctor;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * <PRE>
+ * Call this if you want to check whether there are any unclosed resultsets
+ * It logs stack traces to help find piece of code
+ * where a DBReader was opened but not closed.
+ * </PRE>
+ */
+ public static synchronized void checkOpenResultSets()
+ {
+ Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get();
+ if (openResultSets != null && openResultSets.isEmpty() == false)
+ {
+ // we have found a(n) open result set(s). Now show the stack trace(s)
+ Object keySet[] = openResultSets.keySet().toArray();
+ for (int i = 0; i < keySet.length; i++)
+ {
+ Exception stackException = openResultSets.get(keySet[i]);
+ log.error("A DBReader was not closed. Stack of opening code is ", stackException);
+ }
+ openResultSets.clear();
+ }
+ }
+}
\ No newline at end of file