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