Package org.apache.turbine.om.peer

Source Code of org.apache.turbine.om.peer.BasePeer

package org.apache.turbine.om.peer;

/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001 The Apache Software Foundation.  All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by the
*        Apache Software Foundation (http://www.apache.org/)."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
*    "Apache Turbine" must not be used to endorse or promote products
*    derived from this software without prior written permission. For
*    written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
*    "Apache Turbine", nor may "Apache" appear in their name, without
*    prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation.  For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/

import com.workingdogs.village.Column;
import com.workingdogs.village.DataSet;
import com.workingdogs.village.KeyDef;
import com.workingdogs.village.QueryDataSet;
import com.workingdogs.village.Record;
import com.workingdogs.village.Schema;
import com.workingdogs.village.TableDataSet;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.turbine.TurbineConstants;
import org.apache.turbine.om.NumberKey;
import org.apache.turbine.om.ObjectKey;
import org.apache.turbine.om.SimpleKey;
import org.apache.turbine.om.StringKey;
import org.apache.turbine.services.db.TurbineDB;
import org.apache.turbine.services.logging.Logger;
import org.apache.turbine.services.logging.TurbineLogging;
import org.apache.turbine.services.resources.TurbineResources;
import org.apache.turbine.util.Log;
import org.apache.turbine.util.StringStackBuffer;
import org.apache.turbine.util.db.Criteria;
import org.apache.turbine.util.db.IdGenerator;
import org.apache.turbine.util.db.Query;
import org.apache.turbine.util.db.SqlExpression;
import org.apache.turbine.util.db.adapter.DB;
import org.apache.turbine.util.db.map.ColumnMap;
import org.apache.turbine.util.db.map.DatabaseMap;
import org.apache.turbine.util.db.map.MapBuilder;
import org.apache.turbine.util.db.map.TableMap;
import org.apache.turbine.util.db.pool.DBConnection;

/**
* This is the base class for all Peer classes in the system.  Peer
* classes are responsible for isolating all of the database access
* for a specific business object.  They execute all of the SQL
* against the database.  Over time this class has grown to include
* utility methods which ease execution of cross-database queries and
* the implementation of concrete Peers.
*
* @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
* @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
* @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a>
* @version $Id: BasePeer.java,v 1.63 2001/05/23 15:12:05 mikeh Exp $
*/
public abstract class BasePeer
{
    /** Constant criteria key to reference ORDER BY columns. */
    public static final String ORDER_BY = "ORDER BY";

    /*
     * Constant criteria key to remove Case Information from
     * search/ordering criteria.
     */
    public static final String IGNORE_CASE = "IgNOrE cAsE";

    /** Classes that implement this class should override this value. */
    public static final String TABLE_NAME = "TABLE_NAME";

    /** The Turbine default MapBuilder. */
    public static final String DEFAULT_MAP_BUILDER =
        "org.apache.turbine.util.db.map.TurbineMapBuilder";

    /** Hashtable that contains the cached mapBuilders. */
    private static Hashtable mapBuilders = new Hashtable(5);

    /**
     * Converts a hashtable to a byte array for storage/serialization.
     *
     * @param hash The Hashtable to convert.
     * @return A byte[] with the converted Hashtable.
     * @exception Exception, a generic exception.
     */
    public static byte[] hashtableToByteArray( Hashtable hash )
        throws Exception
    {
        Hashtable saveData = new Hashtable(hash.size());
        String key = null;
        Object value = null;
        byte[] byteArray = null;

        Enumeration keys = hash.keys();
        while(keys.hasMoreElements())
        {
            key = (String) keys.nextElement();
            value = hash.get(key);
            if ( value instanceof Serializable )
                saveData.put ( key, value );
        }

        ByteArrayOutputStream baos = null;
        BufferedOutputStream bos = null;
        ObjectOutputStream out = null;
        try
        {
            // These objects are closed in the finally.
            baos = new ByteArrayOutputStream();
            bos = new BufferedOutputStream(baos);
            out = new ObjectOutputStream(bos);

            out.writeObject(saveData);
            out.flush();
            bos.flush();
            byteArray = baos.toByteArray();
        }
        finally
        {
            if (out != null) out.close();
            if (bos != null) bos.close();
            if (baos != null) baos.close();
        }
        return byteArray;
    }

    /**
     * Sets up a Schema for a table.  This schema is then normally
     * used as the argument for initTableColumns().
     *
     * @param tableName The name of the table.
     * @return A Schema.
     */
    public static Schema initTableSchema(String tableName)
    {
        return initTableSchema(tableName, null);
    }

    /**
     * Sets up a Schema for a table.  This schema is then normally
     * used as the argument for initTableColumns
     *
     * @param tableName The propery name for the database in the
     * Turbineresources file.
     * @param dbName The name of the database.
     * @return A Schema.
     */
    public static Schema initTableSchema(String tableName,
                                         String dbName)
    {
        Schema schema = null;
        DBConnection db = null;

        try
        {
            if (dbName == null)
            {
                // Get a connection to the db.
                db = TurbineDB.getConnection();
            }
            else
            {
                // Get a connection to the db.
                db = TurbineDB.getConnection( dbName );
            }

            Connection connection = db.getConnection();

            schema = new Schema().schema(connection, tableName);
        }
        catch(Exception e)
        {
            Log.error(e);
            throw new Error("Error in BasePeer.initTableSchema(" +
                            tableName + "): " +
                            e.getMessage());
        }
        finally
        {
            try
            {
                TurbineDB.releaseConnection(db);
            }
            catch(Exception e)
            {
            }
        }
        return schema;
    }

    /**
     * Creates a Column array for a table based on its Schema.
     *
     * @param schema A Schema object.
     * @return A Column[].
     */
    public static Column[] initTableColumns(Schema schema)
    {
        Column[] columns = null;
        try
        {
            int numberOfColumns = schema.numberOfColumns();
            columns = new Column[numberOfColumns];
            for (int i=0; i<numberOfColumns; i++)
            {
                columns[i] = schema.column(i+1);
            }
        }
        catch(Exception e)
        {
            Log.error(e);
            throw new Error("Error in BasePeer.initTableColumns(): " +
                            e.getMessage());
        }
        return columns;
    }

    /**
     * Convenience method to create a String array of column names.
     *
     * @param columns A Column[].
     * @return A String[].
     */
    public static String[] initColumnNames(Column[] columns)
    {
        String[] columnNames = null;
        columnNames = new String[columns.length];
        for (int i = 0; i < columns.length; i++)
        {
            columnNames[i] = columns[i].name().toUpperCase();
        }
        return columnNames;
    }

    /**
     * Convenience method to create a String array of criteria keys.
     * Primary use is with TurbineUserPeer.
     *
     * @param tableName Name of table.
     * @param columnNames A String[].
     * @return A String[].
     */
    public static String[] initCriteriaKeys(String tableName,
                                            String[] columnNames)
    {
        String[] keys = new String[columnNames.length];
        for(int i = 0; i < columnNames.length; i++)
        {
            keys[i] = tableName + "." + columnNames[i].toUpperCase();
        }
        return keys;
    }

    /**
     * Begin a transaction.  This method will fallback gracefully to
     * return a normal connection, if the database being accessed does
     * not support transactions.
     *
     * @param dbName Name of database.
     * @return The DBConnection for the transaction.
     * @exception Exception, a generic exception.
     */
    public static DBConnection beginTransaction(String dbName)
        throws Exception
    {
        DBConnection dbCon = TurbineDB.getConnection( dbName );
        if ( dbCon.getConnection().getMetaData().supportsTransactions() )
        {
            dbCon.setAutoCommit(false);
        }
       
        return dbCon;
    }

    /**
     * Commit a transaction.  This method takes care of releasing the
     * connection after the commit.  in databases that do not support
     * transactions, it only returns the connection.
     *
     * @param dbCon The DBConnection for the transaction.
     * @exception Exception, a generic exception.
     */
    public static void commitTransaction(DBConnection dbCon)
        throws Exception
    {
        if ( dbCon.getConnection().getMetaData().supportsTransactions() )
        {
            dbCon.commit();
            dbCon.setAutoCommit(true);
        }

        // Release the connection to the pool.
        TurbineDB.releaseConnection( dbCon );
    }

    /**
     * Roll back a transaction in databases that support transactions.
     * It also releases the connection.  in databases that do not support
     * transactions, this method will log the attempt and release the
     * connection.  
     *
     * @param dbCon The DBConnection for the transaction.
     * @exception Exception, a generic exception.
     */
    public static void rollBackTransaction(DBConnection dbCon)
        throws Exception
    {
        if ( dbCon.getConnection().getMetaData().supportsTransactions() )
        {
            dbCon.rollback();
            dbCon.setAutoCommit(true);
        }
        else
        {
            Log.error("An attempt was made to rollback a transaction but the"
                + " database did not allow the operation to be rolled back.");
        }

        // Release the connection to the pool.
        TurbineDB.releaseConnection( dbCon );
    }


    /**
     * Convenience method that uses straight JDBC to delete multiple
     * rows.  Village throws an Exception when multiple rows are
     * deleted.
     *
     * @param dbCon A DBConnection.
     * @param table The table to delete records from.
     * @param column The column in the where clause.
     * @param value The value of the column.
     * @exception Exception, a generic exception.
     */
    public static void deleteAll( DBConnection dbCon,
                                  String table,
                                  String column,
                                  int value )
        throws Exception
    {
        Connection conn = dbCon.getConnection();
        Statement statement = null;

        try
        {
            statement = conn.createStatement();

            StringBuffer query = new StringBuffer();
            query.append( "DELETE FROM " )
            .append( table )
            .append( " WHERE " )
            .append( column )
            .append( " = " )
            .append( value );

            statement.executeUpdate( query.toString() );
        }
        finally
        {
            if (statement != null) statement.close();
        }
    }

    /**
     * Convenience method that uses straight JDBC to delete multiple
     * rows.  Village throws an Exception when multiple rows are
     * deleted.  This method attempts to get the default database from
     * the pool.
     *
     * @param table The table to delete records from.
     * @param column The column in the where clause.
     * @param value The value of the column.
     * @exception Exception, a generic exception.
     */
    public static void deleteAll( String table,
                                  String column,
                                  int value )
        throws Exception
    {
        DBConnection dbCon = null;
        try
        {
            // Get a connection to the db.
            dbCon = TurbineDB.getConnection();

            deleteAll( dbCon, table, column, value );
        }
        finally
        {
            TurbineDB.releaseConnection(dbCon);
        }
    }

    /**
     * Method to perform deletes based on values and keys in a
     * Criteria.
     *
     * @param criteria The criteria to use.
     * @exception Exception, a generic exception.
     */
    public static void doDelete(Criteria criteria)
        throws Exception
    {
        DBConnection dbCon = null;
        try
        {
            // Get a connection to the db.
            dbCon = TurbineDB.getConnection( criteria.getDbName() );
            doDelete(criteria, dbCon);
        }
        finally
        {
            TurbineDB.releaseConnection(dbCon);
        }
    }

    /**
     * Method to perform deletes based on values and keys in a
     * Criteria.
     *
     * @param criteria The criteria to use.
     * @param dbCon A DBConnection.
     * @exception Exception, a generic exception.
     */
    public static void doDelete(Criteria criteria,
                                DBConnection dbCon)
        throws Exception
    {
        DB db = TurbineDB.getDB( criteria.getDbName() );
        DatabaseMap dbMap = TurbineDB.getDatabaseMap( criteria.getDbName() );
        Connection connection = dbCon.getConnection();

        // Set up a list of required tables and add extra entries to
        // criteria if directed to delete all related records.
        // StringStackBuffer.add() only adds element if it is unique.
        StringStackBuffer tables = new StringStackBuffer();
        Enumeration e = criteria.keys();
        while(e.hasMoreElements())
        {
            String key = (String)e.nextElement();
            Criteria.Criterion c = criteria.getCriterion(key);
            String[] tableNames = c.getAllTables();
            for (int i=0; i<tableNames.length; i++)
            {
                String tableName2 = criteria.getTableForAlias(tableNames[i]);
                if ( tableName2 != null )
                {
                    tables.add(
                        new StringBuffer(tableNames[i].length() +
                                         tableName2.length() + 1)
                        .append(tableName2).append(' ').append(tableNames[i])
                        .toString() );
                }
                else
                {
                    tables.add(tableNames[i]);
                }
            }
           
            if ( criteria.isCascade() )
            {
                // This steps thru all the columns in the database.
                TableMap[] tableMaps = dbMap.getTables();
                for (int i=0; i<tableMaps.length; i++)
                {
                    ColumnMap[] columnMaps = tableMaps[i].getColumns();
                    for (int j=0; j<columnMaps.length; j++)
                    {
                        // Only delete rows where the foreign key is
                        // also a primary key.  Other rows need
                        // updateing, but that is not implemented.
                        if ( columnMaps[j].isForeignKey()
                            && columnMaps[j].isPrimaryKey()
                            && key.equals(columnMaps[j].getRelatedName()) )
                        {
                            tables.add(tableMaps[i].getName());
                            criteria.add(columnMaps[j].getFullyQualifiedName(),
                                         criteria.getValue(key));
                        }
                    }
                }
            }
        }

        for (int i=0; i<tables.size(); i++)
        {
            KeyDef kd = new KeyDef();
            StringStackBuffer whereClause = new StringStackBuffer();

            ColumnMap[] columnMaps =
                dbMap.getTable( tables.get(i) ).getColumns();
            for (int j=0; j<columnMaps.length; j++)
            {
                ColumnMap colMap = columnMaps[j];
                if ( colMap.isPrimaryKey() )
                {
                    kd.addAttrib( colMap.getColumnName() );
                }
                String key = new StringBuffer(colMap.getTableName())
                    .append('.').append(colMap.getColumnName()).toString();
                if ( criteria.containsKey(key) )
                {
                    if ( criteria.getComparison(key).equals(Criteria.CUSTOM) )
                    {
                        whereClause.add( criteria.getString(key) );
                    }
                    else
                    {
                        whereClause.add( SqlExpression.build( colMap.getColumnName(),
                            criteria.getValue(key),
                            criteria.getComparison(key),
                            criteria.isIgnoreCase(),
                            db));
                    }
                }
            }

            // Execute the statement.
            TableDataSet tds = null;
            try
            {
                tds = new TableDataSet(connection, tables.get(i), kd);
                String sqlSnippet = whereClause.toString(" AND ");
                TurbineLogging.getLogger(TurbineConstants.SQL_LOG_FACILITY)
                    .debug("BasePeer.doDelete: whereClause=" + sqlSnippet);
                tds.where(sqlSnippet);
                tds.fetchRecords();
                if ( tds.size() > 1 && criteria.isSingleRecord() )
                {
                    handleMultipleRecords(tds);
                }
                for (int j=0; j<tds.size(); j++)
                {
                    Record rec = tds.getRecord(j);
                    rec.markToBeDeleted();
                    rec.save();
                }
            }
            finally
            {
                if (tds != null) tds.close();
            }
        }
    }

    /**
     * Method to perform inserts based on values and keys in a
     * Criteria.
     *
     * <p>
     *
     * If the primary key is auto incremented the data in Criteria
     * will be inserted and the auto increment value will be returned.
     *
     * <p>
     *
     * If the primary key is included in Criteria then that value will
     * be used to insert the row.
     *
     * <p>
     *
     * If no primary key is included in Criteria then we will try to
     * figure out the primary key from the database map and insert the
     * row with the next available id using util.db.IDBroker.
     *
     * <p>
     *
     * If no primary key is defined for the table the values will be
     * inserted as specified in Criteria and -1 will be returned.
     *
     * @param criteria Object containing values to insert.
     * @return An Object which is the id of the row that was inserted
     * (if the table has a primary key) or null (if the table does not
     * have a primary key).
     * @exception Exception, a generic exception.
     */
    public static ObjectKey doInsert(Criteria criteria)
        throws Exception
    {
        DBConnection dbCon = null;
        ObjectKey id = null;

        // Transaction stuff added for postgres.
        boolean doTransaction = (TurbineDB.getDB(criteria.getDbName()).
            objectDataNeedsTrans() &&
            criteria.containsObjectColumn(criteria.getDbName()));

        try
        {
            // Get a connection to the db.
            if (doTransaction)
            {
                dbCon = beginTransaction(criteria.getDbName());
            }
            else
            {
                dbCon = TurbineDB.getConnection( criteria.getDbName() );
            }
            id = doInsert(criteria, dbCon);
        }
        finally
        {
            if (doTransaction)
            {
                commitTransaction(dbCon);
            }
            else
            {
                TurbineDB.releaseConnection(dbCon);
            }
        }
        return id;
    }


    /**
     * Method to perform inserts based on values and keys in a
     * Criteria.
     *
     * <p>
     *
     * If the primary key is auto incremented the data in Criteria
     * will be inserted and the auto increment value will be returned.
     *
     * <p>
     *
     * If the primary key is included in Criteria then that value will
     * be used to insert the row.
     *
     * <p>
     *
     * If no primary key is included in Criteria then we will try to
     * figure out the primary key from the database map and insert the
     * row with the next available id using util.db.IDBroker.
     *
     * <p>
     *
     * If no primary key is defined for the table the values will be
     * inserted as specified in Criteria and null will be returned.
     *
     * @param criteria Object containing values to insert.
     * @param dbCon A DBConnection.
     * @return An Object which is the id of the row that was inserted
     * (if the table has a primary key) or null (if the table does not
     * have a primary key).
     * @exception Exception, a generic exception.
     */
    public static ObjectKey doInsert(Criteria criteria,
                                     DBConnection dbCon)
        throws Exception
    {
        SimpleKey id = null;

        // Get the table name and method for determining the primary
        // key value.
        String tableName = null;
        Enumeration keys = criteria.keys();
        if (keys.hasMoreElements())
        {
            tableName = criteria.getTableName((String)keys.nextElement());
        }
        else
        {
            throw new Exception("Database insert attempted without anything specified to insert");
        }
        DatabaseMap dbMap = TurbineDB.getDatabaseMap( criteria.getDbName() );
        TableMap tableMap = dbMap.getTable(tableName);
        Object keyInfo = tableMap.getPrimaryKeyMethodInfo();
        IdGenerator keyGen = tableMap.getIdGenerator();

        ColumnMap pk = getPrimaryKey(criteria);
        // only get a new key value if you need to
        // the reason is that a primary key might be defined
        // but you are still going to set its value. for example:
        // a join table where both keys are primary and you are
        // setting both columns with your own values
        boolean info = false;
       
        if (!criteria.containsKey(pk.getFullyQualifiedName()))
        {
            // If the keyMethod is SEQUENCE or IDBROKERTABLE, get the id
            // before the insert.
           
            if (keyGen.isPriorToInsert())
            {
                if ( pk.getType() instanceof Number )
                {
                    id = new NumberKey( tableMap.getIdGenerator()
                        .getIdAsBigDecimal(dbCon.getConnection(), keyInfo) );
                }
                else
                {
                    id = new StringKey( tableMap.getIdGenerator()
                        .getIdAsString(dbCon.getConnection(), keyInfo) );
                }
                criteria.add( pk.getFullyQualifiedName(), id );
            }
        }

        // Set up Village for the insert.
        TableDataSet tds = null;
        try
        {
            tds = new TableDataSet(dbCon.getConnection(), tableName );
            Record rec = tds.addRecord();
            insertOrUpdateRecord(rec, tableName, criteria);
        }
        finally
        {
            if (tds != null) tds.close();
        }

        // If the primary key column is auto-incremented, get the id
        // now.
        if ((keyGen != null) && (keyGen.isPostInsert()))
        {
            if ( pk.getType() instanceof Number )
            {
                id = new NumberKey( tableMap.getIdGenerator()
                    .getIdAsBigDecimal(dbCon.getConnection(), keyInfo) );
            }
            else
            {
                id = new StringKey( tableMap.getIdGenerator()
                    .getIdAsString(dbCon.getConnection(), keyInfo) );
            }
        }

        return id;
    }

    /**
     * Grouping of code used in both doInsert() and doUpdate()
     * methods.  Sets up a Record for saving.
     *
     * @param rec A Record.
     * @param tableName Name of table.
     * @param criteria A Criteria.
     * @exception Exception, a generic exception.
     */
    private static void insertOrUpdateRecord(Record rec,
                                             String tableName,
                                             Criteria criteria)
        throws Exception
    {
        DatabaseMap dbMap = TurbineDB.getDatabaseMap( criteria.getDbName() );

        ColumnMap[] columnMaps = dbMap.getTable( tableName ).getColumns();
        boolean shouldSave = false;
        for (int j=0; j<columnMaps.length; j++)
        {
            ColumnMap colMap = columnMaps[j];
            String key = new StringBuffer(colMap.getTableName())
                .append('.').append(colMap.getColumnName()).toString();
            if ( criteria.containsKey(key) )
            {
                // A village Record.setValue( String, Object ) would
                // be nice here.
                Object obj = criteria.getValue(key);
                if ( obj instanceof SimpleKey )
                {
                    obj = ((SimpleKey)obj).getValue();
                }
                if (obj == null)
                {
                    rec.setValueNull(colMap.getColumnName());
                }   
                else if ( obj instanceof String )
                    rec.setValue( colMap.getColumnName(),
                                  (String)obj );
                else if ( obj instanceof Integer)
                    rec.setValue( colMap.getColumnName(),
                                  criteria.getInt(key) );
                else if ( obj instanceof BigDecimal)
                    rec.setValue( colMap.getColumnName(),
                                  (BigDecimal)obj );
                else if ( obj instanceof Long)
                    rec.setValue( colMap.getColumnName(),
                                  criteria.getLong(key) );
                else if ( obj instanceof java.util.Date)
                    rec.setValue( colMap.getColumnName(),
                                  (java.util.Date)obj );
                else if ( obj instanceof Float)
                    rec.setValue( colMap.getColumnName(),
                                  criteria.getFloat(key) );
                else if ( obj instanceof Double)
                    rec.setValue( colMap.getColumnName(),
                                  criteria.getDouble(key) );
                else if ( obj instanceof Hashtable )
                    rec.setValue( colMap.getColumnName(),
                                  hashtableToByteArray( (Hashtable)obj ) );
                else if ( obj instanceof byte[])
                    rec.setValue( colMap.getColumnName(),
                                  (byte[])obj);
                else if ( obj instanceof Boolean)
                    rec.setValue( colMap.getColumnName(),
                                   criteria.getBoolean(key) ? 1 : 0);
                shouldSave = true;
             }
        }
        if ( shouldSave )
            rec.save();
        else
           throw new Exception ( "BasePeer.doInsert() - Nothing to insert" );
    }

    /**
     * Method to create an SQL query based on values in a Criteria.
     *
     * @param criteria A Criteria.
     * @exception Exception Trouble creating the query string.
     */
    public static String createQueryString( Criteria criteria )
        throws Exception
    {
        Query query = new Query();
        DB db = TurbineDB.getDB( criteria.getDbName() );
        DatabaseMap dbMap = TurbineDB.getDatabaseMap( criteria.getDbName() );

        StringStackBuffer selectModifiers = query.getSelectModifiers();
        StringStackBuffer selectClause = query.getSelectClause();
        StringStackBuffer fromClause = query.getFromClause();
        StringStackBuffer whereClause = query.getWhereClause();
        StringStackBuffer orderByClause = query.getOrderByClause();

        StringStackBuffer orderBy = criteria.getOrderByColumns();
        boolean ignoreCase = criteria.isIgnoreCase();
        StringStackBuffer select = criteria.getSelectColumns();
        Hashtable aliases = criteria.getAsColumns();
        StringStackBuffer modifiers = criteria.getSelectModifiers();

        for (int i=0; i<modifiers.size(); i++)
        {
            selectModifiers.add( modifiers.get(i) );
        }

        for (int i=0; i<select.size(); i++)
        {
            String columnName = select.get(i);
            String tableName = null;
            selectClause.add(columnName);
            int parenPos = columnName.indexOf('(');
            if (parenPos == -1)
            {
                tableName = columnName.substring(0,
                                                 columnName.indexOf('.') );
            }
            else
            {
                tableName = columnName.substring(parenPos + 1,
                                                 columnName.indexOf('.') );
            }
            String tableName2 = criteria.getTableForAlias(tableName);
            if ( tableName2 != null )
            {
                fromClause.add(
                    new StringBuffer(tableName.length() +
                                     tableName2.length() + 1)
                    .append(tableName2).append(' ').append(tableName)
                    .toString() );
            }
            else
            {
                fromClause.add(tableName);
            }
        }

        Iterator it = aliases.keySet().iterator();
        while(it.hasNext())
        {
          String key = (String)it.next();
          selectClause.add((String)aliases.get(key) + " AS " + key);
        }

        Enumeration e = criteria.keys();
        while (e.hasMoreElements())
        {
            String key = (String)e.nextElement();
            Criteria.Criterion criterion =
                (Criteria.Criterion)criteria.getCriterion(key);
            Criteria.Criterion[] someCriteria =
                criterion.getAttachedCriterion();
            String table = null;
            for (int i=0; i<someCriteria.length; i++)
            {
                String tableName = someCriteria[i].getTable();
                table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                    table = tableName;
                }

                boolean ignorCase = (criteria.isIgnoreCase() &&
                    (dbMap.getTable(table).getColumn(
                    someCriteria[i].getColumn()).getType() instanceof String));
               
                someCriteria[i].setIgnoreCase(ignorCase);
            }
               
            criterion.setDB(db);
            whereClause.add( criterion.toString() );

        }

        List join = criteria.getJoinL();
        if ( join != null)
        {
            for ( int i=0; i<join.size(); i++ )
            {
                String join1 = (String)join.get(i);
                String join2 = (String)criteria.getJoinR().get(i);
           
                String tableName = join1.substring(0, join1.indexOf('.'));
                String table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                }

                int dot =  join2.indexOf('.');
                tableName = join2.substring(0, dot);
                table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                    table = tableName;
                }

                boolean ignorCase = (criteria.isIgnoreCase() &&
                    (dbMap.getTable(table).getColumn(
                        join2.substring(dot+1, join2.length()) )
                    .getType() instanceof String));
               
                whereClause.add(
                    SqlExpression.buildInnerJoin(join1, join2,
                                                 ignorCase, db) );
            }
        }

        if ( orderBy != null && orderBy.size() > 0)
        {
            // Check for each String/Character column and apply
            // toUpperCase().
            for (int i=0; i<orderBy.size(); i++)
            {
                String orderByColumn = orderBy.get(i);
                String table = orderByColumn.substring(0,orderByColumn.indexOf('.') );
                // See if there's a space (between the column list and sort
                // order in ORDER BY table.column DESC).
                int spacePos = orderByColumn.indexOf(' ');
                String columnName;
                if (spacePos == -1)
                    columnName = orderByColumn.substring(orderByColumn.indexOf('.') + 1);
                else
                    columnName = orderByColumn.substring(orderByColumn.indexOf('.') + 1, spacePos);
                ColumnMap column = dbMap.getTable(table).getColumn( columnName );
                if ( column.getType() instanceof String )
                {
                    if (spacePos == -1)
                        orderByClause.add( db.ignoreCaseInOrderBy(orderByColumn) );
                    else
                        orderByClause.add( db.ignoreCaseInOrderBy(orderByColumn.substring(0, spacePos)) + orderByColumn.substring(spacePos) );
                }
                else
                {
                    orderByClause.add(orderByColumn);
                }
            }
        }

        // Limit the number of rows returned.
        int limit = criteria.getLimit();
        int offset = criteria.getOffset();
        String limitString = null;
        if ( offset > 0 && db.supportsNativeOffset() )
        {
            switch(db.getLimitStyle())
            {
                case DB.LIMIT_STYLE_MYSQL:
                    limitString = new StringBuffer().append(offset)
                                                    .append(", ")
                                                    .append(limit)
                                                    .toString();
                    break;
                case DB.LIMIT_STYLE_POSTGRES:
                    limitString = new StringBuffer().append(limit)
                                                    .append(", ")
                                                    .append(offset)
                                                    .toString();
                    break;
            }

            // Now set the criteria's limit and offset to return the
            // full resultset since the results are limited on the
            // server.
            criteria.setLimit(-1);
            criteria.setOffset(0);
        }
        else if (limit > 0 && db.supportsNativeLimit() )
        {
            limitString = String.valueOf(limit);

            // Now set the criteria's limit to return the full
            // resultset since the results are limited on the server.
            criteria.setLimit(-1);
        }

        if (limitString != null) query.setLimit(limitString);

        String sql = query.toString();
        TurbineLogging.getLogger(TurbineConstants.SQL_LOG_FACILITY).debug(sql);
        return sql;
    }


    /**
     * Old method for performing a SELECT.  Returns all results.
     *
     * @param criteria A Criteria.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector doSelect(Criteria criteria)
        throws Exception
    {
        // Transaction stuff added for postgres.
        Vector results = null;
        if (TurbineDB.getDB(criteria.getDbName())
              .objectDataNeedsTrans() &&
              criteria.containsObjectColumn(criteria.getDbName()))
        {
            DBConnection dbCon = beginTransaction(criteria.getDbName());
            try
            {
                results = executeQuery( createQueryString(criteria),
                                        criteria.isSingleRecord(), dbCon );
                commitTransaction(dbCon);
            }
            catch (Exception e)
            {
                // make sure to return connection
                rollBackTransaction(dbCon);
                throw e;
            }
        }
        else
        {
            results = executeQuery( createQueryString(criteria),
                                    criteria.getDbName(),
                                    criteria.isSingleRecord() );
        }
        return results;
    }

    /**
     * Old method for performing a SELECT.  Returns all results.
     *
     * @param criteria A Criteria.
     * @param dbCon A DBConnection.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector doSelect(Criteria criteria,
                                  DBConnection dbCon)
        throws Exception
    {
        return executeQuery( createQueryString(criteria),
                             criteria.isSingleRecord(), dbCon );
    }


    /**
     * Utility method which executes a given sql statement.  This
     * method should be used for select statements only.  Use
     * executeStatement for update, insert, and delete operations.
     *
     * @param queryString A String with the sql statement to execute.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString)
        throws Exception
    {
        return executeQuery(queryString, TurbineDB.getDefaultDB(), false);
    }

    /**
     * Utility method which executes a given sql statement.  This
     * method should be used for select statements only.  Use
     * executeStatement for update, insert, and delete operations.
     *
     * @param queryString A String with the sql statement to execute.
     * @param dbName The database to connect to.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString,
                                      String dbName)
        throws Exception
    {
        return executeQuery(queryString, dbName, false);
    }

    /**
     * Method for performing a SELECT.  Returns all results.
     *
     * @param queryString A String with the sql statement to execute.
     * @param dbName The database to connect to.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString,
                                      String dbName,
                                      boolean singleRecord)
        throws Exception
    {
        return executeQuery(queryString, 0, -1, dbName, singleRecord);
    }

    /**
     * Method for performing a SELECT.  Returns all results.
     *
     * @param queryString A String with the sql statement to execute.
     * @param dbName The database to connect to.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @param dbCon A DBConnection.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString,
                                      boolean singleRecord,
                                      DBConnection dbCon)
        throws Exception
    {
        return executeQuery(queryString, 0, -1, singleRecord, dbCon);
    }


    /**
     * Method for performing a SELECT.
     *
     * @param queryString A String with the sql statement to execute.
     * @param start The first row to return.
     * @param numberOfResults The number of rows to return.
     * @param dbName The database to connect to.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString,
                                      int start,
                                      int numberOfResults,
                                      String dbName,
                                      boolean singleRecord)
        throws Exception
    {
        DBConnection db = null;
        Vector results = null;
        try
        {
            // get a connection to the db
            db = TurbineDB.getConnection( dbName );
            // execute the query
            results = executeQuery(queryString, start, numberOfResults,
                                   singleRecord, db);
        }
        finally
        {
            TurbineDB.releaseConnection(db);
        }
        return results;
    }

    /**
     * Method for performing a SELECT.  Returns all results.
     *
     * @param queryString A String with the sql statement to execute.
     * @param start The first row to return.
     * @param numberOfResults The number of rows to return.
     * @param dbName The database to connect to.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @param dbCon A DBConnection.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector executeQuery(String queryString,
                                      int start,
                                      int numberOfResults,
                                      boolean singleRecord,
                                      DBConnection dbCon)
        throws Exception
    {
        Connection connection = dbCon.getConnection();

        QueryDataSet qds = null;
        Vector results = new Vector();
        try
        {
            // execute the query
            qds = new QueryDataSet( connection, queryString );
            results = getSelectResults( qds, start, numberOfResults,
                                        singleRecord);
        }
        finally
        {
            if (qds != null) qds.close();
        }
        return results;
    }


    /**
     * Returns all records in a QueryDataSet as a Vector of Record
     * objects.  Used for functionality like util.db.LargeSelect.
     *
     * @param qds A QueryDataSet.
     * @return Vector of Record objects.
     * @exception Exception, a generic exception.
     */
    public static Vector getSelectResults( QueryDataSet qds )
        throws Exception
    {
        return getSelectResults( qds, 0, -1, false);
    }

    /**
     * Returns all records in a QueryDataSet as a Vector of Record
     * objects.  Used for functionality like util.db.LargeSelect.
     *
     * @param qds A QueryDataSet.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @exception Exception, a generic exception.
     */
    public static Vector getSelectResults( QueryDataSet qds,
                                           boolean singleRecord )
        throws Exception
    {
        return getSelectResults(qds, 0, -1, singleRecord);
    }

    /**
     * Returns numberOfResults records in a QueryDataSet as a Vector
     * of Record objects.  Starting at record 0.  Used for
     * functionality like util.db.LargeSelect.
     *
     * @param qds A QueryDataSet.
     * @param numberOfResults The number of results to return.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @exception Exception, a generic exception.
     */
    public static Vector getSelectResults( QueryDataSet qds,
                                           int numberOfResults,
                                           boolean singleRecord )
        throws Exception
    {
        Vector results = null;
        if (numberOfResults != 0)
        {
            results = getSelectResults(qds, 0, numberOfResults, singleRecord);
        }                                
        return results;
    }

    /**
     * Returns numberOfResults records in a QueryDataSet as a Vector
     * of Record objects.  Starting at record start.  Used for
     * functionality like util.db.LargeSelect.
     *
     * @param qds A QueryDataSet.
     * @param start where to start retrieving Records.
     * @param numberOfResults The number of results to return.
     * @param singleRecord Whether or not we want to select only a
     * single record.
     * @exception Exception, a generic exception.
     */
    public static Vector getSelectResults( QueryDataSet qds,
                                           int start,
                                           int numberOfResults,
                                           boolean singleRecord )
        throws Exception
    {
        Vector results;
        if ( numberOfResults <= 0 )
        {
            results = new Vector();
            qds.fetchRecords();
        }
        else
        {
            results = new Vector(numberOfResults);
            qds.fetchRecords(start, numberOfResults);
        }
        if ( qds.size() > 1 && singleRecord )
        {
            handleMultipleRecords(qds);
        }

        // Return a Vector of Record objects.
        for ( int i=0; i<qds.size(); i++ )
        {
            Record rec = qds.getRecord(i);
            results.addElement(rec);
        }
        return results;
    }

    /**
     * Helper method which returns the primary key contained
     * in the given Criteria object.
     *
     * @param criteria A Criteria.
     * @return ColumnMap if the Criteria object contains a primary
     * key, or null if it doesn't.
     * @exception Exception, a generic exception.
     */
    private static ColumnMap getPrimaryKey(Criteria criteria)
        throws Exception
    {
        // Assume all the keys are for the same table.
        String key = (String)criteria.keys().nextElement();

        String table = criteria.getTableName(key);
        ColumnMap pk = null;

        if ( !table.equals("") )
        {
            DatabaseMap dbMap = TurbineDB.getDatabaseMap(criteria.getDbName());
            if (dbMap == null)
                throw new Exception ("dbMap is null");
            if (dbMap.getTable(table) == null)
                throw new Exception ("dbMap.getTable() is null");

            ColumnMap[] columns = dbMap.getTable(table).getColumns();

            for (int i=0; i<columns.length; i++)
            {
                if ( columns[i].isPrimaryKey() )
                {
                    pk = columns[i];
                    break;
                }
            }
        }
        return pk;
    }

    /**
     * Use this method for performing an update of the kind:
     *
     * <p>
     *
     * "WHERE primary_key_id = an int"
     *
     * <p>
     *
     * Convenience method used to update rows in the DB.  Checks if a
     * <i>single</i> int primary key is specified in the Criteria
     * object and uses it to perform the udpate.  If no primary key is
     * specified an Exception will be thrown.
     *
     * <p>
     *
     * To perform an update with non-primary key fields in the WHERE
     * clause use doUpdate(criteria, criteria).
     *
     * @param updateValues A Criteria object containing values used in
     * set clause.
     * @exception Exception, a generic exception.
     */
    public static void doUpdate(Criteria updateValues)
        throws Exception
    {
        // Transaction stuff added for postgres.
        boolean doTransaction = (TurbineDB.getDB(updateValues.getDbName())
            .objectDataNeedsTrans() &&
            updateValues.containsObjectColumn(updateValues.getDbName()));
        DBConnection db = null;
        try
        {
            // Get a connection to the db.
            if (doTransaction)
            {
                db = beginTransaction(updateValues.getDbName());
            }
            else
            {
                db = TurbineDB.getConnection( updateValues.getDbName() );
            }

            doUpdate(updateValues, db);
        }
        finally
        {
            if (doTransaction)
            {
                commitTransaction(db);
            }
            else
            {
                TurbineDB.releaseConnection(db);
            }
        }
    }


    /**
     * Use this method for performing an update of the kind:
     *
     * <p>
     *
     * "WHERE primary_key_id = an int"
     *
     * <p>
     *
     * Convenience method used to update rows in the DB.  Checks if a
     * <i>single</i> int primary key is specified in the Criteria
     * object and uses it to perform the udpate.  If no primary key is
     * specified an Exception will be thrown.
     *
     * <p>
     *
     * To perform an update with non-primary key fields in the WHERE
     * clause use doUpdate(criteria, criteria).
     *
     * @param updateValues A Criteria object containing values used in
     * set clause.
     * @param dbCon A DBConnection.
     * @exception Exception, a generic exception.
     */
    public static void doUpdate(Criteria updateValues,
                                DBConnection dbCon)
        throws Exception
    {
        ColumnMap pk = getPrimaryKey(updateValues);
        Criteria selectCriteria = null;

        if ( pk != null &&
             updateValues.containsKey(pk.getFullyQualifiedName()) )
        {
            selectCriteria = new Criteria(2);
            selectCriteria.put( pk.getFullyQualifiedName(),
                                updateValues.remove(pk.getFullyQualifiedName()) );
        }
        else
        {
            throw new Exception("BasePeer.doUpdate(criteria) - no PK specified");
        }

        doUpdate( selectCriteria, updateValues, dbCon );
    }

    /**
     * Use this method for performing an update of the kind:
     *
     * <p>
     *
     * WHERE some_column = some value AND could_have_another_column =
     * another value AND so on...
     *
     * <p>
     *
     * Method used to update rows in the DB.  Rows are selected based
     * on selectCriteria and updated using values in updateValues.
     *
     * @param selectCriteria A Criteria object containing values used
     * in where clause.
     * @param updateValues A Criteria object containing values used in
     * set clause.
     * @exception Exception, a generic exception.
     */
    public static void doUpdate(Criteria selectCriteria,
                                Criteria updateValues)
        throws Exception
    {
        // Transaction stuff added for postgres.
        boolean doTransaction = (TurbineDB.getDB(updateValues.getDbName())
            .objectDataNeedsTrans() &&
            updateValues.containsObjectColumn(selectCriteria.getDbName()));
        DBConnection db = null;
        try
        {
            // Get a connection to the db.
            if (doTransaction)
                db = beginTransaction(selectCriteria.getDbName());
            else
                db = TurbineDB.getConnection( selectCriteria.getDbName() );

            doUpdate(selectCriteria, updateValues, db);
        }
        finally
        {
            if (doTransaction)
                commitTransaction(db);
            else
                TurbineDB.releaseConnection(db);
        }
    }


    /**
     * Use this method for performing an update of the kind:
     *
     * <p>
     *
     * WHERE some_column = some value AND could_have_another_column =
     * another value AND so on.
     *
     * <p>
     *
     * Method used to update rows in the DB.  Rows are selected based
     * on selectCriteria and updated using values in updateValues.
     *
     * @param selectCriteria A Criteria object containing values used
     * in where clause.
     * @param updateValues A Criteria object containing values used in
     * set clause.
     * @param dbCon A DBConnection.
     * @exception Exception, a generic exception.
     */
    public static void doUpdate(Criteria selectCriteria,
                                Criteria updateValues,
                                DBConnection dbCon)
        throws Exception
    {
        DB db = TurbineDB.getDB( selectCriteria.getDbName() );
        DatabaseMap dbMap =
            TurbineDB.getDatabaseMap( selectCriteria.getDbName() );
        Connection connection = dbCon.getConnection();

        // Set up a list of required tables.  StringStackBuffer.add()
        // only adds element if it is unique.
        StringStackBuffer tables = new StringStackBuffer();
        Enumeration e = selectCriteria.keys();
        while(e.hasMoreElements())
        {
            tables.add(selectCriteria.getTableName( (String)e.nextElement() ));
        }

        for (int i=0; i<tables.size(); i++)
        {
            KeyDef kd = new KeyDef();
            StringStackBuffer whereClause = new StringStackBuffer();
            DatabaseMap tempDbMap = dbMap;

            ColumnMap[] columnMaps =
                tempDbMap.getTable( tables.get(i) ).getColumns();
            for (int j=0; j<columnMaps.length; j++)
            {
                ColumnMap colMap = columnMaps[j];
                if ( colMap.isPrimaryKey() )
                {
                    kd.addAttrib( colMap.getColumnName() );
                }
                String key = new StringBuffer(colMap.getTableName())
                    .append('.').append(colMap.getColumnName()).toString();
                if ( selectCriteria.containsKey(key) )
                {
                    if ( selectCriteria.getComparison( key ).equals(Criteria.CUSTOM) )
                    {
                        whereClause.add( selectCriteria.getString( key ));
                    }
                    else
                    {
                        whereClause.add( SqlExpression.build( colMap.getColumnName(),
                            selectCriteria.getValue( key ),
                            selectCriteria.getComparison( key ),
                            selectCriteria.isIgnoreCase(),
                            db ));
                    }
                }
            }
            TableDataSet tds = null;
            try
            {
                // Get affected records.
                tds = new TableDataSet(connection, tables.get(i), kd );
                String sqlSnippet = whereClause.toString(" AND ");
                TurbineLogging.getLogger(TurbineConstants.SQL_LOG_FACILITY)
                    .debug("BasePeer.doUpdate: whereClause=" + sqlSnippet);
                tds.where(sqlSnippet);
                tds.fetchRecords();

                if ( tds.size() > 1 && selectCriteria.isSingleRecord() )
                {
                    handleMultipleRecords(tds);
                }
                for (int j=0; j<tds.size(); j++)
                {
                    Record rec = tds.getRecord(j);
                    insertOrUpdateRecord(rec, tables.get(i), updateValues);
                }
            }
            finally
            {
                if (tds != null) tds.close();
            }
        }
    }

    /**
     * Utility method which executes a given sql statement.  This
     * method should be used for update, insert, and delete
     * statements.  Use executeQuery() for selects.
     *
     * @param stmt A String with the sql statement to execute.
     * @return The number of rows affected.
     * @exception Exception, a generic exception.
     */
    public static int executeStatement(String stmt)
        throws Exception
    {
        return executeStatement(stmt, TurbineDB.getDefaultDB());
    }

    /**
     * Utility method which executes a given sql statement.  This
     * method should be used for update, insert, and delete
     * statements.  Use executeQuery() for selects.
     *
     * @param stmt A String with the sql statement to execute.
     * @param dbName Name of database to connect to.
     * @return The number of rows affected.
     * @exception Exception, a generic exception.  */
    public static int executeStatement(String stmt,
                                       String dbName)
        throws Exception
    {
        DBConnection db = null;
        int rowCount = -1;
        try
        {
            // Get a connection to the db.
            db = TurbineDB.getConnection( dbName );

            rowCount = executeStatement( stmt, db );
        }
        finally
        {
            TurbineDB.releaseConnection(db);
        }
        return rowCount;
    }


    /**
     * Utility method which executes a given sql statement.  This
     * method should be used for update, insert, and delete
     * statements.  Use executeQuery() for selects.
     *
     * @param stmt A String with the sql statement to execute.
     * @param dbCon A DBConnection.
     * @return The number of rows affected.
     * @exception Exception, a generic exception.
     */
    public static int executeStatement(String stmt,
                                       DBConnection dbCon)
        throws Exception
    {
        Connection con = dbCon.getConnection();
        Statement statement = null;
        int rowCount = -1;

        try
        {
            statement = con.createStatement();
            rowCount = statement.executeUpdate( stmt );
        }
        finally
        {
            if (statement != null) statement.close();
        }
        return rowCount;
    }


    /**
     * If the user specified that (s)he only wants to retrieve a
     * single record and multiple records are retrieved, this method
     * is called to handle the situation.  The default behavior is to
     * throw an exception, but subclasses can override this method as
     * needed.
     *
     * @param ds The DataSet which contains multiple records.
     * @exception Exception Couldn't handle multiple records.
     */
    protected static void handleMultipleRecords(DataSet ds)
        throws Exception
    {
        throw new Exception("Criteria expected single Record and Multiple Records were selected.");
    }

    /**
     * @deprecated Use the better-named handleMultipleRecords() instead.
     */
    protected static void handleMultiple(DataSet ds)
        throws Exception
    {
        handleMultipleRecords(ds);
    }

    /**
     * This method returns the MapBuilder specified in the
     * TurbineResources.properties file. By default, this is
     * org.apache.turbine.util.db.map.TurbineMapBuilder.
     *
     * @return A MapBuilder.
     */
    public static MapBuilder getMapBuilder()
    {
        return getMapBuilder(TurbineResources.getString("database.maps.builder",
                DEFAULT_MAP_BUILDER).trim());
    }

    /**
     * This method returns the MapBuilder specified in the name
     * parameter.  You should pass in the full path to the class, ie:
     * org.apache.turbine.util.db.map.TurbineMapBuilder.  The
     * MapBuilder instances are cached in this class for speed.
     *
     * @return A MapBuilder, or null (and logs the error) if the
     * MapBuilder was not found.
     */
    public static MapBuilder getMapBuilder(String name)
    {
        try
        {
            MapBuilder mb = (MapBuilder)mapBuilders.get(name);
            // Use the 'double-check pattern' for syncing
            //  caching of the MapBuilder.
            if (mb == null)
            {
                synchronized(mapBuilders)
                {
                   mb = (MapBuilder)mapBuilders.get(name);
                   if (mb == null)
                  {           
                      mb = (MapBuilder)Class.forName(name).newInstance();
                      // Cache the MapBuilder before it is built.
                      mapBuilders.put(name, mb);
                  }
               }
            }
           
            // Build the MapBuilder in its own synchronized block to
            //  avoid locking up the whole Hashtable while doing so.
            // Note that *all* threads need to do a sync check on isBuilt()
            //  to avoid grabing an uninitialized MapBuilder. This, however,
            //  is a relatively fast operation.
            synchronized(mb)
            {
               if (!mb.isBuilt())
               {
                  try
                  {
                     mb.doBuild();
                  }
                  catch ( Exception e )
                  {
                      // need to think about whether we'd want to remove
                      //  the MapBuilder from the cache if it can't be
                      //  built correctly...?  pgo
                      throw e;
                  }
               }
            }
            return mb;
        }
        catch(Exception e)
        {
            // Have to catch possible exceptions because method is
            // used in initialization of Peers.  Log the exception and
            // return null.
            Log.error("BasePeer.MapBuilder failed trying to instantiate: " +
                      name, e);
        }
        return null;
    }


    /**
     * Performs a SQL <code>select</code> using a PreparedStatement.
     *
     * @exception Exception Error performing database query.
     */
    public static Vector doPSSelect(Criteria criteria, DBConnection dbCon)
        throws Exception
    {
        Vector v = null;
       
        StringBuffer qry = new StringBuffer();
        Vector params = new Vector(criteria.size());
       
        createPreparedStatement (criteria, qry, params);
       
        PreparedStatement stmt = null;
        try
        {
            stmt = dbCon.getConnection().prepareStatement(qry.toString());

            for (int i = 0; i < params.size(); i++)
            {
                Object param = params.get(i);
                if (param instanceof java.sql.Date)
                {
                    stmt.setDate(i + 1, (java.sql.Date)param);
                }
                else
                {
                    stmt.setString(i + 1, param.toString());
                }
            }
       
            QueryDataSet qds = null;
            try
            {
                qds = new QueryDataSet( stmt.executeQuery() );
                v = getSelectResults(qds);
            }
            finally
            {
                if (qds != null)
                {
                    qds.close();
                }
            }
        }
        finally
        {
            if (stmt != null)
            {
                stmt.close();
            }
        }
       
        return v;
    }


    /**
     * Do a Prepared Statement select according to the given criteria
     */
    public static Vector doPSSelect(Criteria criteria) throws Exception
    {
        DBConnection dbCon = TurbineDB.getConnection( criteria.getDbName() );
        Vector v = null;

        try
        {
            v = doPSSelect (criteria,dbCon);
        }
        finally
        {
            TurbineDB.releaseConnection( dbCon );
        }

        return v;
    }
       

    /**
     * Create a new PreparedStatement.  It builds a string representation
     * of a query and a list of PreparedStatement parameters.
     */
    public static void createPreparedStatement(Criteria criteria,
                                               StringBuffer queryString,
                                               List params)
        throws Exception
    {
        DB db = TurbineDB.getDB( criteria.getDbName() );
        DatabaseMap dbMap = TurbineDB.getDatabaseMap( criteria.getDbName() );
       
        Query query = new Query();
       
        StringStackBuffer selectModifiers = query.getSelectModifiers();
        StringStackBuffer selectClause = query.getSelectClause();
        StringStackBuffer fromClause = query.getFromClause();
        StringStackBuffer whereClause = query.getWhereClause();
        StringStackBuffer orderByClause = query.getOrderByClause();

        StringStackBuffer orderBy = criteria.getOrderByColumns();
        boolean ignoreCase = criteria.isIgnoreCase();
        StringStackBuffer select = criteria.getSelectColumns();
        Hashtable aliases = criteria.getAsColumns();
        StringStackBuffer modifiers = criteria.getSelectModifiers();

        for (int i=0; i<modifiers.size(); i++)
        {
            selectModifiers.add( modifiers.get(i) );
        }

        for (int i=0; i<modifiers.size(); i++)
        {
            selectModifiers.add( modifiers.get(i) );
        }

        for (int i=0; i<select.size(); i++)
        {
            String columnName = select.get(i);
            String tableName = null;
            selectClause.add(columnName);
            int parenPos = columnName.indexOf('(');
            if (parenPos == -1)
            {
                tableName = columnName.substring(0,
                                                 columnName.indexOf('.') );
            }
            else
            {
                tableName = columnName.substring(parenPos + 1,
                                                 columnName.indexOf('.') );
            }
            String tableName2 = criteria.getTableForAlias(tableName);
            if ( tableName2 != null )
            {
                fromClause.add(
                    new StringBuffer(tableName.length() +
                                     tableName2.length() + 1)
                    .append(tableName2).append(' ').append(tableName)
                    .toString() );
            }
            else
            {
                fromClause.add(tableName);
            }
        }

       
        Iterator it = aliases.keySet().iterator();
        while(it.hasNext())
        {
          String key = (String)it.next();
          selectClause.add((String)aliases.get(key) + " AS " + key);
        }

        Enumeration e = criteria.keys();
        while (e.hasMoreElements())
        {
            String key = (String)e.nextElement();
            Criteria.Criterion criterion =
                (Criteria.Criterion)criteria.getCriterion(key);
            Criteria.Criterion[] someCriteria =
                criterion.getAttachedCriterion();
               
            String table = null;
            for (int i=0; i<someCriteria.length; i++)
            {
                String tableName = someCriteria[i].getTable();
                table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                    table = tableName;
                }

                boolean ignorCase = (criteria.isIgnoreCase() &&
                    (dbMap.getTable(table).getColumn(
                    someCriteria[i].getColumn()).getType() instanceof String));
               
                someCriteria[i].setIgnoreCase(ignorCase);
            }
               
            criterion.setDB(db);
            StringBuffer sb = new StringBuffer();
            criterion.appendPsTo (sb,params);
            whereClause.add( sb.toString() );

        }
       
        List join = criteria.getJoinL();
        if ( join != null)
        {
            for ( int i=0; i<join.size(); i++ )
            {
                String join1 = (String)join.get(i);
                String join2 = (String)criteria.getJoinR().get(i);
           
                String tableName = join1.substring(0, join1.indexOf('.'));
                String table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                }

                int dot =  join2.indexOf('.');
                tableName = join2.substring(0, dot);
                table = criteria.getTableForAlias(tableName);
                if ( table != null )
                {
                    fromClause.add(
                        new StringBuffer(tableName.length() +
                                         table.length() + 1)
                        .append(table).append(' ').append(tableName)
                        .toString() );
                }
                else
                {
                    fromClause.add(tableName);
                    table = tableName;
                }

                boolean ignorCase = (criteria.isIgnoreCase() &&
                    (dbMap.getTable(table).getColumn(
                        join2.substring(dot+1, join2.length()) )
                    .getType() instanceof String));
               
                whereClause.add(
                    SqlExpression.buildInnerJoin(join1, join2,
                                                 ignorCase, db) );
            }
        }

        if ( orderBy != null && orderBy.size() > 0)
        {
            // Check for each String/Character column and apply
            // toUpperCase().
            for (int i=0; i<orderBy.size(); i++)
            {
                String orderByColumn = orderBy.get(i);
                String table = orderByColumn.substring(0,orderByColumn.indexOf('.') );
                // See if there's a space (between the column list and sort
                // order in ORDER BY table.column DESC).
                int spacePos = orderByColumn.indexOf(' ');
                String columnName;
                if (spacePos == -1)
                    columnName = orderByColumn.substring(orderByColumn.indexOf('.') + 1);
                else
                    columnName = orderByColumn.substring(orderByColumn.indexOf('.') + 1, spacePos);
                ColumnMap column = dbMap.getTable(table).getColumn( columnName );
                if ( column.getType() instanceof String )
                {
                    if (spacePos == -1)
                        orderByClause.add( db.ignoreCaseInOrderBy(orderByColumn) );
                    else
                        orderByClause.add( db.ignoreCaseInOrderBy(orderByColumn.substring(0, spacePos)) + orderByColumn.substring(spacePos) );
                }
                else
                {
                    orderByClause.add(orderByColumn);
                }
            }
        }

        // Limit the number of rows returned.
        int limit = criteria.getLimit();
        int offset = criteria.getOffset();
        String limitString = null;
        if ( offset > 0 && db.supportsNativeOffset() )
        {
            switch(db.getLimitStyle())
            {
                case DB.LIMIT_STYLE_MYSQL:
                    limitString = new StringBuffer().append(offset)
                                                    .append(", ")
                                                    .append(limit)
                                                    .toString();
                    break;
                case DB.LIMIT_STYLE_POSTGRES:
                    limitString = new StringBuffer().append(limit)
                                                    .append(", ")
                                                    .append(offset)
                                                    .toString();
                    break;
            }

            // Now set the criteria's limit and offset to return the
            // full resultset since the results are limited on the
            // server.
            criteria.setLimit(-1);
            criteria.setOffset(0);
        }
        else if (limit > 0 && db.supportsNativeLimit() )
        {
            limitString = String.valueOf(limit);

            // Now set the criteria's limit to return the full
            // resultset since the results are limited on the server.
            criteria.setLimit(-1);
        }

        if (limitString != null) query.setLimit(limitString);

        String sql = query.toString();
        TurbineLogging.getLogger(TurbineConstants.SQL_LOG_FACILITY).debug(sql);
        queryString.append (sql);
    }

}
TOP

Related Classes of org.apache.turbine.om.peer.BasePeer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.