/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.cmp.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import javax.ejb.EJBException;
import javax.ejb.NoSuchEntityException;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
import org.jboss.logging.Logger;
import org.jboss.deployment.DeploymentException;
/**
* JDBCLoadEntityCommand loads the data for an instance from the table.
* This command implements specified eager loading. For CMP 2.x, the
* entity can be configured to only load some of the fields, which is
* helpful for entitys with lots of data.
*
* @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
* @author <a href="mailto:on@ibis.odessa.ua">Oleg Nitz</a>
* @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
* @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
* @author <a href="mailto:shevlandj@kpi.com.au">Joe Shevland</a>
* @author <a href="mailto:justin@j-m-f.demon.co.uk">Justin Forder</a>
* @author <a href="mailto:dirk@jboss.de">Dirk Zimmermann</a>
* @author <a href="mailto:danch@nvisia.com">danch (Dan Christopherson)</a>
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version $Revision: 81030 $
*/
public final class JDBCLoadEntityCommand
{
private final JDBCStoreManager manager;
private final JDBCEntityBridge entity;
private final Logger log;
private final JDBCFunctionMappingMetaData rowLockingTemplate;
public JDBCLoadEntityCommand(JDBCStoreManager manager) throws DeploymentException
{
this.manager = manager;
entity = (JDBCEntityBridge) manager.getEntityBridge();
boolean rowLocking = entity.getMetaData().hasRowLocking();
rowLockingTemplate = rowLocking ? entity.getMetaData().getTypeMapping().getRowLockingTemplate() : null;
// Create the Log
log = Logger.getLogger(
this.getClass().getName() +
"." +
manager.getMetaData().getName());
}
/**
* Loads entity.
* If failIfNotFound is true and entity wasn't found then NoSuchEntityException is thrown.
* Otherwise, if entity wasn't found, returns false.
* If entity was loaded successfully return true.
* @param ctx - entity context;
* @param failIfNotFound - whether to fail if entity wasn't found;
* @return true if entity was loaded, false - otherwise.
*/
public boolean execute(EntityEnterpriseContext ctx, boolean failIfNotFound)
{
return execute(null, ctx, failIfNotFound);
}
/**
* Loads entity or required field. If entity not found throws NoSuchEntityException.
* @param requiredField - required field or null;
* @param ctx - the corresponding context;
*/
public void execute(JDBCCMPFieldBridge requiredField, EntityEnterpriseContext ctx)
{
execute(requiredField, ctx, true);
}
/**
* Loads entity or required field.
* If failIfNotFound is set to true, then NoSuchEntityException is thrown if the
* entity wasn't found.
* If failIfNotFound is false then if the entity wasn't found returns false,
* if the entity was loaded successfully, returns true.
* @param requiredField - required field;
* @param ctx - entity context;
* @param failIfNotFound - whether to fail if entity wasn't loaded.
* @return true if entity was loaded, false - otherwise.
*/
private boolean execute(JDBCCMPFieldBridge requiredField,
EntityEnterpriseContext ctx,
boolean failIfNotFound)
{
// load the instance primary key fields into the context
Object id = ctx.getId();
// TODO: when exactly do I need to do the following?
entity.injectPrimaryKeyIntoInstance(ctx, id);
// get the read ahead cache
ReadAheadCache readAheadCache = manager.getReadAheadCache();
// load any preloaded fields into the context
if(readAheadCache.load(ctx))
{
if(requiredField == null || (requiredField != null && requiredField.isLoaded(ctx)))
{
return true;
}
}
// get the finder results associated with this context, if it exists
ReadAheadCache.EntityReadAheadInfo info = readAheadCache.getEntityReadAheadInfo(id);
// determine the fields to load
JDBCEntityBridge.FieldIterator loadIter = entity.getLoadIterator(requiredField, info.getReadAhead(), ctx);
if(!loadIter.hasNext())
return true;
// get the keys to load
List loadKeys = info.getLoadKeys();
// generate the sql
String sql = (rowLockingTemplate != null ? getRawLockingSQL(loadIter, loadKeys.size()) : getSQL(loadIter, loadKeys.size()));
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
// create the statement
if (log.isDebugEnabled())
{
log.debug("Executing SQL: " + sql);
}
// get the connection
con = entity.getDataSource().getConnection();
ps = con.prepareStatement(sql);
// Set the fetch size of the statement
if (entity.getFetchSize() > 0)
{
ps.setFetchSize(entity.getFetchSize());
}
// set the parameters
int paramIndex = 1;
for (int i = 0; i < loadKeys.size(); i++)
{
paramIndex = entity.setPrimaryKeyParameters(ps, paramIndex, loadKeys.get(i));
}
// execute statement
rs = ps.executeQuery();
// load results
boolean mainEntityLoaded = false;
Object[] ref = new Object[1];
while (rs.next())
{
// reset the column index for this row
int index = 1;
// ref must be reset to null before load
ref[0] = null;
// if we are loading more then one entity, load the pk from the row
Object pk = null;
if (loadKeys.size() > 1)
{
// load the pk
index = entity.loadPrimaryKeyResults(rs, index, ref);
pk = ref[0];
}
// is this the main entity or a preload entity
if (loadKeys.size() == 1 || pk.equals(id))
{
// main entity; load the values into the context
loadIter.reset();
while(loadIter.hasNext())
{
JDBCCMPFieldBridge field = loadIter.next();
index = field.loadInstanceResults(rs, index, ctx);
field.setClean(ctx);
}
mainEntityLoaded = true;
}
else
{
// preload entity; load the values into the read ahead cahce
loadIter.reset();
while(loadIter.hasNext())
{
JDBCCMPFieldBridge field = loadIter.next();
// ref must be reset to null before load
ref[0] = null;
// load the result of the field
index = field.loadArgumentResults(rs, index, ref);
// cache the field value
readAheadCache.addPreloadData(pk, field, ref[0]);
}
}
}
// clear LOAD_REQUIRED flag
loadIter.removeAll();
// did we load the main results
if (!mainEntityLoaded)
{
if (failIfNotFound)
throw new NoSuchEntityException("Entity not found: primaryKey=" + ctx.getId());
else
return false;
}
else
return true;
}
catch (EJBException e)
{
throw e;
}
catch (Exception e)
{
throw new EJBException("Load failed", e);
}
finally
{
JDBCUtil.safeClose(rs);
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
}
private String getSQL(JDBCEntityBridge.FieldIterator loadIter, int keyCount)
{
StringBuffer sql = new StringBuffer(250);
sql.append(SQLUtil.SELECT);
// if we are loading more then one entity we need to add the primry
// key to the load fields to match up the results with the correct entity.
JDBCFieldBridge[] primaryKeyFields = entity.getPrimaryKeyFields();
if (keyCount > 1)
{
SQLUtil.getColumnNamesClause(primaryKeyFields, sql);
sql.append(SQLUtil.COMMA);
}
SQLUtil.getColumnNamesClause(loadIter, sql);
sql.append(SQLUtil.FROM)
.append(entity.getQualifiedTableName())
.append(SQLUtil.WHERE);
//
// where clause
String pkWhere = SQLUtil.getWhereClause(primaryKeyFields, new StringBuffer(50)).toString();
sql.append('(').append(pkWhere).append(')');
for (int i = 1; i < keyCount; i++)
{
sql.append(SQLUtil.OR).append('(').append(pkWhere).append(')');
}
return sql.toString();
}
private String getRawLockingSQL(JDBCEntityBridge.FieldIterator loadIter, int keyCount)
{
//
// column names clause
StringBuffer columnNamesClause = new StringBuffer(250);
// if we are loading more then one entity we need to add the primry
// key to the load fields to match up the results with the correct
// entity.
if (keyCount > 1)
{
SQLUtil.getColumnNamesClause(entity.getPrimaryKeyFields(), columnNamesClause);
columnNamesClause.append(SQLUtil.COMMA);
}
SQLUtil.getColumnNamesClause(loadIter, columnNamesClause);
//
// table name clause
String tableName = entity.getQualifiedTableName();
//
// where clause
String whereClause = SQLUtil.
getWhereClause(entity.getPrimaryKeyFields(), new StringBuffer(50)).toString();
if (keyCount > 0)
{
StringBuffer sb = new StringBuffer((whereClause.length() + 6) * keyCount + 4);
for (int i = 0; i < keyCount; i++)
{
if (i > 0)
sb.append(SQLUtil.OR);
sb.append('(').append(whereClause).append(')');
}
whereClause = sb.toString();
}
String[] args = new String[]{
columnNamesClause.toString(),
tableName,
whereClause,
null // order by
};
return rowLockingTemplate.getFunctionSql(args, new StringBuffer(300)).toString();
}
}