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