Package org.apache.ojb.broker.accesslayer

Source Code of org.apache.ojb.broker.accesslayer.JdbcAccessImpl

package org.apache.ojb.broker.accesslayer;

/* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.KeyConstraintViolatedException;
import org.apache.ojb.broker.OptimisticLockException;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PersistenceBrokerSQLException;
import org.apache.ojb.broker.core.ValueContainer;
import org.apache.ojb.broker.metadata.ArgumentDescriptor;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.FieldDescriptor;
import org.apache.ojb.broker.metadata.ProcedureDescriptor;
import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
import org.apache.ojb.broker.query.Query;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.broker.util.sequence.SequenceManagerException;

/**
* JdbcAccess is responsible for establishing performing
* SQL Queries against remote Databases.
* It hides all knowledge about JDBC from the BrokerImpl
*
* @author <a href="mailto:thma@apache.org">Thomas Mahler</a>
* @version $Id: JdbcAccessImpl.java,v 1.22.2.3 2005/03/04 22:39:05 mkalen Exp $
*/
public class JdbcAccessImpl implements JdbcAccess
{
    private static final String SQL_STATE_KEY_VIOLATED = "23000";
    private static final String SQL_STATE_FK_VIOLATED = "23505";
    /*
        X/OPEN codes within class 23:
            23000  INTEGRITY CONSTRAINT VIOLATION
            23001  RESTRICT VIOLATION
            23502  NOT NULL VIOLATION
            23503  FOREIGN KEY VIOLATION
            23505  UNIQUE VIOLATION
            23514  CHECK VIOLATION
    */

    /**
     * The logger used.
     */
    protected Logger logger;

    /**
     * The broker in use.
     */
    protected PersistenceBroker broker;

    /**
     * constructor is private, use getInstance to get
     * the singleton instance of this class
     */
    public JdbcAccessImpl(PersistenceBroker broker)
    {
        this.broker = broker;
        logger = LoggerFactory.getLogger(this.getClass());
    }

    /**
     * performs a DELETE operation against RDBMS.
     * @param cld ClassDescriptor providing mapping information.
     * @param obj The object to be deleted.
     */
    public void executeDelete(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("executeDelete: " + obj);
        }

        PreparedStatement stmt = null;
        try
        {
            stmt = broker.serviceStatementManager().getDeleteStatement(cld);
            if (stmt == null)
            {
                logger.error("getDeleteStatement returned a null statement");
                throw new PersistenceBrokerException("JdbcAccessImpl: getDeleteStatement returned a null statement");
            }

            broker.serviceStatementManager().bindDelete(stmt, cld, obj);
            if (logger.isDebugEnabled())
                logger.debug("executeDelete: " + stmt);

            // @todo: clearify semantics
            // thma: the following check is not secure. The object could be deleted *or* changed.
            // if it was deleted it makes no sense to throw an OL exception.
            // does is make sense to throw an OL exception if the object was changed?
            if (stmt.executeUpdate() == 0 && cld.isLocking()) //BRJ
            {
                throw new OptimisticLockException("Object has been modified or deleted by someone else", obj);
            }

            // Harvest any return values.
            harvestReturnValues(cld.getDeleteProcedure(), obj, stmt);
        }
        catch (OptimisticLockException e)
        {
            // Don't log as error
            if (logger.isDebugEnabled())
                logger.debug("OptimisticLockException during the execution of delete: "
                        + e.getMessage(), e);
            throw e;
        }
        catch (PersistenceBrokerException e)
        {
            logger.error("PersistenceBrokerException during the execution of delete: "
                    + e.getMessage(), e);
            throw e;
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of the delete (for "
                + cld.getClassOfObject().getName()
                + "): " + e.getMessage();
            logger.error(msg, e);
            throw new PersistenceBrokerSQLException("JdbcAccessImpl: " + msg, e);
        }
        finally
        {
            broker.serviceStatementManager().closeResources(stmt, null);
        }
    }

    /**
     * Performs a DELETE operation based on the given {@link Query} against RDBMS.
     * @param query the query string.
     * @param cld ClassDescriptor providing JDBC information.
     */
    public void executeDelete(Query query, ClassDescriptor cld) throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("executeDelete (by Query): " + query);
        }

        PreparedStatement stmt = null;

        try
        {
            String sql = this.broker.serviceSqlGenerator().getPreparedDeleteStatement(query, cld);
            stmt = broker.serviceStatementManager().getPreparedStatement(cld, sql, false);

            broker.serviceStatementManager().bindStatement(stmt, query, cld, 1);
            if (logger.isDebugEnabled())
                logger.debug("executeDelete (by Query): " + stmt);

            stmt.executeUpdate();
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of delete by query (for "
                + cld.getClassOfObject().getName()
                + "): " + e.getMessage();
            logger.error(msg,e);
            throw new PersistenceBrokerSQLException(msg, e);
        }
        finally
        {
            broker.serviceStatementManager().closeResources(stmt, null);
        }
    }

    /**
     * performs an INSERT operation against RDBMS.
     * @param obj The Object to be inserted as a row of the underlying table.
     * @param cld ClassDescriptor providing mapping information.
     */
    public void executeInsert(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("executeInsert: " + obj);
        }

        PreparedStatement stmt = null;
        try
        {
            stmt = broker.serviceStatementManager().getInsertStatement(cld);
            if (stmt == null)
            {
                logger.error("getInsertStatement returned a null statement");
                throw new PersistenceBrokerException("getInsertStatement returned a null statement");
            }

            broker.serviceStatementManager().bindInsert(stmt, cld, obj);
            if (logger.isDebugEnabled())
                logger.debug("executeInsert: " + stmt);

            stmt.executeUpdate();
            // if database Identity Columns are used, query the id from database
            // other SequenceManager implementations will ignore this call
            broker.serviceSequenceManager().afterStore(this, cld, obj);

            // Harvest any return values.
            harvestReturnValues(cld.getInsertProcedure(), obj, stmt);
        }
        catch (PersistenceBrokerException e)
        {
            logger.error("PersistenceBrokerException during the execution of the insert: " + e.getMessage(), e);
            throw e;
        }
        catch(SequenceManagerException e)
        {
            throw new PersistenceBrokerException("Can't lookup new database Identity Column value", e);
        }
        catch (SQLException e)
        {
            final String stateCode = e.getSQLState();
            // Build a detailed error message
            StringBuffer msg = new StringBuffer("SQL failure while insert object data for class ");
            try
            {
                msg.append(cld.getClassNameOfObject())
                        .append(", PK of the given object is [");
                FieldDescriptor[] fields = cld.getPkFields();
                for (int i = 0; i < fields.length; i++)
                {
                    msg.append(" ")
                            .append(fields[i].getPersistentField().getName())
                            .append("=")
                            .append(fields[i].getPersistentField().get(obj));
                }
                msg.append("], object was " + obj);
                msg.append(", exception message is [").append(e.getMessage()).append("]");
                msg.append(", SQL code [").append(stateCode).append("]");
            }
            catch (Exception ignore)
            {
                msg.append("- Sorry, can't generate a more detailed message");
            }

            /**
             * throw a specific type of runtime exception for a key constraint.
             */
            if (SQL_STATE_KEY_VIOLATED.equals(stateCode)
                ||
                SQL_STATE_FK_VIOLATED.equals(stateCode))
            {
                throw new KeyConstraintViolatedException(msg.toString(), e);
            }
            else
            {
                throw new PersistenceBrokerSQLException(msg.toString(), e);
            }
        }
        finally
        {
            broker.serviceStatementManager().closeResources(stmt, null);
        }
    }

    /**
     * performs a SELECT operation against RDBMS.
     * @param query the query string.
     * @param cld ClassDescriptor providing JDBC information.
     */
    public ResultSetAndStatement executeQuery(Query query, ClassDescriptor cld) throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("executeQuery: " + query);
        }
        /*
     * MBAIRD: we should create a scrollable resultset if the start at
     * index or end at index is set
     */
        boolean scrollable = ((query.getStartAtIndex() > Query.NO_START_AT_INDEX) || (query.getEndAtIndex() > Query.NO_END_AT_INDEX));
        /*
     * OR if the prefetching of relationships is being used.
     */
        if (query != null && query.getPrefetchedRelationships() != null && !query.getPrefetchedRelationships().isEmpty())
        {
            scrollable = true;
        }
        ResultSetAndStatement retval = null;
        try
        {
            String sql = broker.serviceSqlGenerator().getPreparedSelectStatement(query, cld);
            PreparedStatement stmt = broker.serviceStatementManager().getPreparedStatement(cld, sql, scrollable);

            broker.serviceStatementManager().bindStatement(stmt, query, cld, 1);
            if (logger.isDebugEnabled())
                logger.debug("executeQuery: " + stmt);

            ResultSet rs = stmt.executeQuery();

            retval = new ResultSetAndStatement(
                    broker.serviceConnectionManager().getSupportedPlatform(), stmt, rs);
            return retval;
        }
        catch (PersistenceBrokerException e)
        {
            logger.error("PersistenceBrokerException during the execution of the query: " + e.getMessage(), e);
            /*
       * MBAIRD: error condition could result in our
       * ResultSetAndStatement not being returned, and not being closed
       * since it is opened before the try loop, we should release it if
       * there is a problem.
       */
            if (retval != null)
            {
                retval.close();
            }
            throw e;
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of the query (for "
                    + cld.getClassOfObject().getName()
                    + "): " + e.getMessage();
            logger.error(msg, e);
            /*
       * MBAIRD: error condition could result in our
       * ResultSetAndStatement not being returned, and not being closed
       * since it is opened before the try loop, we should release it if
       * there is a problem.
       */
            if (retval != null)
            {
                retval.close();
            }
            throw new PersistenceBrokerSQLException(msg, e);
        }
    }

    public ResultSetAndStatement executeSQL(
        String sqlStatement,
        ClassDescriptor cld,
        boolean scrollable)
        throws PersistenceBrokerException
    {
        return executeSQL(sqlStatement, cld, null, scrollable);
    }

    /**
     * performs a SQL SELECT statement against RDBMS.
     * @param sqlStatement the query string.
     * @param cld ClassDescriptor providing meta-information.
     */
    public ResultSetAndStatement executeSQL(
        String sqlStatement,
        ClassDescriptor cld,
        ValueContainer[] values,
        boolean scrollable)
        throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled()) logger.debug("executeSQL: " + sqlStatement);
        StatementManagerIF stmtMan = broker.serviceStatementManager();
        ResultSetAndStatement retval = null;
        try
        {
            PreparedStatement stmt = stmtMan.getPreparedStatement(cld, sqlStatement, scrollable);
            stmtMan.bindValues(stmt, values, 1);
            ResultSet rs = stmt.executeQuery();
            // as we return the resultset for further operations, we cannot release the statement yet.
            // that has to be done by the JdbcAccess-clients (i.e. RsIterator, ProxyRsIterator and PkEnumeration.)
            retval = new ResultSetAndStatement(
                    broker.serviceConnectionManager().getSupportedPlatform(), stmt, rs);
            return retval;
        }
        catch (PersistenceBrokerException e)
        {
            logger.error("PersistenceBrokerException during the execution of the SQL query: " + e.getMessage(), e);

            /**
             * MBAIRD: error condition could result in our ResultSetAndStatement not being returned, and not being closed
             * since it is opened before the try loop, we should release it if there is a problem.
             */
            if (retval != null)
            {
                retval.close();
            }
            throw e;
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of the SQL query: " + sqlStatement
                    + ", message is: " + e.getMessage();
            logger.error(msg, e);
            /**
             * MBAIRD: error condition could result in our ResultSetAndStatement not being returned, and not being closed
             * since it is opened before the try loop, we should release it if there is a problem.
             */
            if (retval != null)
            {
                retval.close();
            }
            throw new PersistenceBrokerSQLException(msg, e);
        }
    }

    public int executeUpdateSQL(String sqlStatement, ClassDescriptor cld)
        throws PersistenceBrokerException
    {
        return executeUpdateSQL(sqlStatement, cld, null, null);
    }

    /**
     * performs a SQL UPDTE, INSERT or DELETE statement against RDBMS.
     * @param sqlStatement the query string.
     * @param cld ClassDescriptor providing meta-information.
     * @return int returncode
     */
    public int executeUpdateSQL(
        String sqlStatement,
        ClassDescriptor cld,
        ValueContainer[] values1,
        ValueContainer[] values2)
        throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
            logger.debug("executeUpdateSQL: " + sqlStatement);

        int result;
        int index;
        PreparedStatement stmt = null;
        StatementManagerIF stmtMan = broker.serviceStatementManager();
        try
        {
            stmt = stmtMan.getPreparedStatement(cld, sqlStatement, Query.NOT_SCROLLABLE);
            index = stmtMan.bindValues(stmt, values1, 1);
            index = stmtMan.bindValues(stmt, values2, index);
            result = stmt.executeUpdate();
        }
        catch (PersistenceBrokerException e)
        {
            logger.error("PersistenceBrokerException during the execution of the Update SQL query: " + e.getMessage(), e);
            throw e;
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of the update SQL query: " + sqlStatement;
            logger.error(msg, e);
            if (SQL_STATE_KEY_VIOLATED.equals(e.getSQLState()))
            {
                throw new KeyConstraintViolatedException(msg, e);
            }
            else
            {
                throw new PersistenceBrokerSQLException(msg, e);
            }
        }
        finally
        {
            stmtMan.closeResources(stmt, null);
        }
        return result;
    }

    /**
     * performs an UPDATE operation against RDBMS.
     * @param obj The Object to be updated in the underlying table.
     * @param cld ClassDescriptor providing mapping information.
     */
    public void executeUpdate(ClassDescriptor cld, Object obj) throws PersistenceBrokerException
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("executeUpdate: " + obj);
        }

        PreparedStatement stmt = null;

        // obj with nothing but key fields is not updated
        if (cld.getNonPkRwFields().length == 0)
        {
            return;
        }
       
        // BRJ: preserve current locking values
        // locking values will be restored in case of exception
        ValueContainer[] oldLockingValues;
        oldLockingValues = cld.getCurrentLockingValues(obj);

        try
        {          
            stmt = broker.serviceStatementManager().getUpdateStatement(cld);
            if (stmt == null)
            {
                logger.error("getUpdateStatement returned a null statement");
                throw new PersistenceBrokerException("getUpdateStatement returned a null statement");
            }

            broker.serviceStatementManager().bindUpdate(stmt, cld, obj);
            if (logger.isDebugEnabled())
                logger.debug("executeUpdate: " + stmt);

            if ((stmt.executeUpdate() == 0) && cld.isLocking()) //BRJ
            {
                throw new OptimisticLockException("Object has been modified by someone else", obj);
            }

            // Harvest any return values.
            harvestReturnValues(cld.getUpdateProcedure(), obj, stmt);
        }
        catch (OptimisticLockException e)
        {
            // Don't log as error
            if (logger.isDebugEnabled())
                logger.debug(
                    "OptimisticLockException during the execution of update: " + e.getMessage(),
                    e);
            throw e;
        }
        catch (PersistenceBrokerException e)
        {
            // BRJ: restore old locking values
            setLockingValues(cld, obj, oldLockingValues);

            logger.error(
                "PersistenceBrokerException during the execution of the update: " + e.getMessage(),
                e);
            throw e;
        }
        catch (SQLException e)
        {
            // BRJ: restore old locking values
            setLockingValues(cld, obj, oldLockingValues);
            String msg = "SQLException during the execution of the update (for a "
                + cld.getClassOfObject().getName()
                + "): "
                + e.getMessage();
            logger.error(msg,e);
            if (SQL_STATE_KEY_VIOLATED.equals(e.getSQLState()))
            {
                throw new KeyConstraintViolatedException(msg, e);
            }
            throw new PersistenceBrokerSQLException(msg, e);
        }
        finally
        {          
            broker.serviceStatementManager().closeResources(stmt, null);
        }
    }

    /**
     * performs a primary key lookup operation against RDBMS and materializes
     * an object from the resulting row. Only skalar attributes are filled from
     * the row, references are not resolved.
     * @param oid contains the primary key info.
     * @param cld ClassDescriptor providing mapping information.
     * @return the materialized object, null if no matching row was found or if
     * any error occured.
     */
    public Object materializeObject(ClassDescriptor cld, Identity oid)
        throws PersistenceBrokerException
    {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        try
        {
            stmt = broker.serviceStatementManager().getSelectByPKStatement(cld);
            if (stmt == null)
            {
                logger.error("getSelectByPKStatement returned a null statement");
                throw new PersistenceBrokerException("getSelectByPKStatement returned a null statement");
            }
            broker.serviceStatementManager().bindSelect(stmt, oid, cld);
            rs = stmt.executeQuery();
            // data available read object, else return null
            if (rs.next())
            {
                Map row = new HashMap();
                cld.getRowReader().readObjectArrayFrom(rs, row);
                return cld.getRowReader().readObjectFrom(row);
            }
            else
            {
                return null;
            }
        }
        catch (PersistenceBrokerException e)
        {
            logger.error(
                "PersistenceBrokerException during the execution of materializeObject: "
                    + e.getMessage(),
                e);
            throw e;
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of materializeObject (for a "
                + cld.getClassOfObject().getName()
                + "): "
                + e.getMessage();
            logger.error(msg,e);
            throw new PersistenceBrokerSQLException(msg, e);
        }
        finally
        {
            broker.serviceStatementManager().closeResources(stmt, rs);
        }
    }

    /**
     * Set the locking values
     * @param cld
     * @param obj
     * @param oldLockingValues
     */
    private void setLockingValues(ClassDescriptor cld, Object obj, ValueContainer[] oldLockingValues)
    {
        FieldDescriptor fields[] = cld.getLockingFields();
       
        for (int i=0; i<fields.length; i++)
        {
            PersistentField field = fields[i].getPersistentField();
            Object lockVal = oldLockingValues[i].getValue();
           
            field.set(obj, lockVal);
        }
    }
   
    /**
     * Harvest any values that may have been returned during the execution
     * of a procedure.
     *
     * @param proc the procedure descriptor that provides info about the procedure
     *      that was invoked.
     * @param obj the object that was persisted
     * @param stmt the statement that was used to persist the object.
     *
     * @throws PersistenceBrokerSQLException if a problem occurs.
     */
    private void harvestReturnValues(
        ProcedureDescriptor proc,
        Object obj,
        PreparedStatement stmt)
        throws PersistenceBrokerSQLException
    {

        // If the procedure descriptor is null or has no return values or
        // if the statement is not a callable statment, then we're done.
        if ((proc == null) || (!proc.hasReturnValues()) || (!(stmt instanceof CallableStatement)))
        {
            return;
        }

        // Set up the callable statement
        CallableStatement callable = (CallableStatement) stmt;

        // This is the index that we'll use to harvest the return value(s).
        int index = 0;

        // If the proc has a return value, then try to harvest it.
        if (proc.hasReturnValue())
        {

            // Increment the index
            index++;

            // Harvest the value.
            this.harvestReturnValue(obj, callable, proc.getReturnValueFieldRef(), index);
        }

        // Check each argument.  If it's returned by the procedure,
        // then harvest the value.
        Iterator iter = proc.getArguments().iterator();
        while (iter.hasNext())
        {
            index++;
            ArgumentDescriptor arg = (ArgumentDescriptor) iter.next();
            if (arg.getIsReturnedByProcedure())
            {
                this.harvestReturnValue(obj, callable, arg.getFieldRef(), index);
            }
        }
    }

    /**
     * Harvest a single value that was returned by a callable statement.
     *
     * @param obj the object that will receive the value that is harvested.
     * @param callable the CallableStatement that contains the value to harvest
     * @param fmd the FieldDescriptor that identifies the field where the
     *      harvested value will be stord.
     * @param index the parameter index.
     *
     * @throws PersistenceBrokerSQLException if a problem occurs.
     */
    private void harvestReturnValue(
        Object obj,
        CallableStatement callable,
        FieldDescriptor fmd,
        int index)
        throws PersistenceBrokerSQLException
    {

        try
        {
            // If we have a field descriptor, then we can harvest
            // the return value.
            if ((callable != null) && (fmd != null) && (obj != null))
            {
                // Get the value and convert it to it's appropriate
                // java type.
                Object value = fmd.getJdbcType().getObjectFromColumn(callable, index);

                // Set the value of the persistent field.
                fmd.getPersistentField().set(obj, fmd.getFieldConversion().sqlToJava(value));

            }
        }
        catch (SQLException e)
        {
            String msg = "SQLException during the execution of harvestReturnValue"
                + " class="
                + obj.getClass().getName()
                + ","
                + " field="
                + fmd.getAttributeName()
                + " : "
                + e.getMessage();
            logger.error(msg,e);
            throw new PersistenceBrokerSQLException(msg, e);
        }
    }
}
TOP

Related Classes of org.apache.ojb.broker.accesslayer.JdbcAccessImpl

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.