/*
* 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.jdbc2.schema;
import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMRFieldBridge2;
import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMPFieldBridge2;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCRelationMetaData;
import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
import org.jboss.deployment.DeploymentException;
import org.jboss.logging.Logger;
import javax.sql.DataSource;
import javax.transaction.Transaction;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.PreparedStatement;
/**
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version <tt>$Revision: 81030 $</tt>
*/
public class RelationTable
implements Table
{
private static final byte CREATED = 1;
private static final byte DELETED = 2;
private final Schema schema;
private final int tableId;
private final DataSource ds;
private final String tableName;
private final JDBCCMRFieldBridge2 leftField;
private final JDBCCMRFieldBridge2 rightField;
private final Logger log;
private String insertSql;
private String deleteSql;
public RelationTable(JDBCCMRFieldBridge2 leftField, JDBCCMRFieldBridge2 rightField, Schema schema, int tableId)
throws DeploymentException
{
this.schema = schema;
this.tableId = tableId;
this.leftField = leftField;
this.rightField = rightField;
JDBCRelationMetaData metadata = leftField.getMetaData().getRelationMetaData();
ds = metadata.getDataSource();
tableName = SQLUtil.fixTableName(metadata.getDefaultTableName(), ds);
log = Logger.getLogger(getClass().getName() + "." + tableName);
// generate sql
insertSql = "insert into " + tableName + " (";
JDBCCMPFieldBridge2[] keyFields = (JDBCCMPFieldBridge2[])this.leftField.getTableKeyFields();
insertSql += keyFields[0].getColumnName();
for(int i = 1; i < keyFields.length; ++i)
{
insertSql += ", " + keyFields[i].getColumnName();
}
keyFields = (JDBCCMPFieldBridge2[])this.rightField.getTableKeyFields();
insertSql += ", " + keyFields[0].getColumnName();
for(int i = 1; i < keyFields.length; ++i)
{
insertSql += ", " + keyFields[i].getColumnName();
}
insertSql += ") values (?";
for(int i = 1; i < this.leftField.getTableKeyFields().length + this.rightField.getTableKeyFields().length; ++i)
{
insertSql += ", ?";
}
insertSql += ")";
log.debug("insert sql: " + insertSql);
deleteSql = "delete from " + tableName + " where ";
keyFields = (JDBCCMPFieldBridge2[])this.leftField.getTableKeyFields();
deleteSql += keyFields[0].getColumnName() + "=?";
for(int i = 1; i < keyFields.length; ++i)
{
deleteSql += " and " + keyFields[i].getColumnName() + "=?";
}
keyFields = (JDBCCMPFieldBridge2[])this.rightField.getTableKeyFields();
deleteSql += " and " + keyFields[0].getColumnName() + "=?";
for(int i = 1; i < keyFields.length; ++i)
{
deleteSql += " and " + keyFields[i].getColumnName() + "=?";
}
log.debug("delete sql: " + deleteSql);
}
// Public
public void addRelation(JDBCCMRFieldBridge2 field1, Object key1, JDBCCMRFieldBridge2 field2, Object key2)
{
View view = getView();
if(field1 == leftField)
{
view.addKeys(key1, key2);
}
else
{
view.addKeys(key2, key1);
}
}
public void removeRelation(JDBCCMRFieldBridge2 field1, Object key1, JDBCCMRFieldBridge2 field2, Object key2)
{
View view = getView();
if(field1 == leftField)
{
view.removeKeys(key1, key2);
}
else
{
view.removeKeys(key2, key1);
}
}
// Table implementation
public int getTableId()
{
return tableId;
}
public String getTableName()
{
return tableName;
}
public Table.View createView(Transaction tx)
{
return new View();
}
// Private
private void delete(View view) throws SQLException
{
if(view.deleted == null)
{
if(log.isTraceEnabled())
{
log.trace("no rows to delete");
}
return;
}
Connection con = null;
PreparedStatement ps = null;
try
{
if(log.isDebugEnabled())
{
log.debug("executing : " + deleteSql);
}
con = ds.getConnection();
ps = con.prepareStatement(deleteSql);
int batchCount = 0;
while(view.deleted != null)
{
RelationKeys keys = view.deleted;
int paramInd = 1;
JDBCCMPFieldBridge2[] keyFields = (JDBCCMPFieldBridge2[])leftField.getTableKeyFields();
for(int pkInd = 0; pkInd < keyFields.length; ++pkInd)
{
JDBCCMPFieldBridge2 pkField = keyFields[pkInd];
Object fieldValue = pkField.getPrimaryKeyValue(keys.leftKey);
paramInd = pkField.setArgumentParameters(ps, paramInd, fieldValue);
}
keyFields = (JDBCCMPFieldBridge2[])rightField.getTableKeyFields();
for(int pkInd = 0; pkInd < keyFields.length; ++pkInd)
{
JDBCCMPFieldBridge2 pkField = keyFields[pkInd];
Object fieldValue = pkField.getPrimaryKeyValue(keys.rightKey);
paramInd = pkField.setArgumentParameters(ps, paramInd, fieldValue);
}
ps.addBatch();
++batchCount;
keys.dereference();
}
ps.executeBatch();
if(view.deleted != null)
{
throw new IllegalStateException("There are still rows to delete!");
}
if(log.isTraceEnabled())
{
log.trace("deleted rows: " + batchCount);
}
}
catch(SQLException e)
{
log.error("Failed to delete view: " + e.getMessage(), e);
throw e;
}
finally
{
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
}
private void insert(View view) throws SQLException
{
if(view.created == null)
{
if(log.isTraceEnabled())
{
log.trace("no rows to insert");
}
return;
}
Connection con = null;
PreparedStatement ps = null;
try
{
if(log.isDebugEnabled())
{
log.debug("executing : " + insertSql);
}
con = ds.getConnection();
ps = con.prepareStatement(insertSql);
int batchCount = 0;
while(view.created != null)
{
RelationKeys keys = view.created;
JDBCCMPFieldBridge2[] keyFields = (JDBCCMPFieldBridge2[])leftField.getTableKeyFields();
int paramInd = 1;
for(int fInd = 0; fInd < keyFields.length; ++fInd)
{
JDBCCMPFieldBridge2 field = keyFields[fInd];
Object fieldValue = field.getPrimaryKeyValue(keys.leftKey);
paramInd = field.setArgumentParameters(ps, paramInd, fieldValue);
}
keyFields = (JDBCCMPFieldBridge2[])rightField.getTableKeyFields();
for(int fInd = 0; fInd < keyFields.length; ++fInd)
{
JDBCCMPFieldBridge2 field = keyFields[fInd];
Object fieldValue = field.getPrimaryKeyValue(keys.rightKey);
paramInd = field.setArgumentParameters(ps, paramInd, fieldValue);
}
ps.addBatch();
++batchCount;
keys.dereference();
}
ps.executeBatch();
if(log.isTraceEnabled())
{
log.trace("inserted rows: " + batchCount);
}
}
catch(SQLException e)
{
log.error("Failed to insert new rows: " + e.getMessage(), e);
throw e;
}
finally
{
JDBCUtil.safeClose(ps);
JDBCUtil.safeClose(con);
}
}
private View getView()
{
return (View) schema.getView(this);
}
// Inner
private class View implements Table.View
{
private RelationKeys created;
private RelationKeys deleted;
// Public
public void addKeys(Object leftKey, Object rightKey)
{
// if it was deleted then dereference
RelationKeys keys = deleted;
while(keys != null)
{
if(keys.equals(leftKey, rightKey))
{
keys.dereference();
return;
}
keys = keys.next;
}
// add to created
keys = new RelationKeys(this, leftKey, rightKey);
if(created != null)
{
keys.next = created;
created.prev = keys;
}
created = keys;
keys.state = CREATED;
}
public void removeKeys(Object leftKey, Object rightKey)
{
// if it was created then dereference
RelationKeys keys = created;
while(keys != null)
{
if(keys.equals(leftKey, rightKey))
{
keys.dereference();
return;
}
keys = keys.next;
}
// add to deleted
keys = new RelationKeys(this, leftKey, rightKey);
if(deleted != null)
{
keys.next = deleted;
deleted.prev = keys;
}
deleted = keys;
keys.state = DELETED;
}
// Table.View implementation
public void flushDeleted(Schema.Views views) throws SQLException
{
delete(this);
}
public void flushCreated(Schema.Views views) throws SQLException
{
insert(this);
}
public void flushUpdated() throws SQLException
{
}
public void beforeCompletion()
{
}
public void committed()
{
}
public void rolledback()
{
}
}
private class RelationKeys
{
private final View view;
private final Object leftKey;
private final Object rightKey;
private byte state;
private RelationKeys next;
private RelationKeys prev;
public RelationKeys(View view, Object leftKey, Object rightKey)
{
this.view = view;
this.leftKey = leftKey;
this.rightKey = rightKey;
}
// Public
public boolean equals(Object leftKey, Object rightKey)
{
return this.leftKey.equals(leftKey) && this.rightKey.equals(rightKey);
}
public void dereference()
{
if(state == CREATED && this == view.created)
{
view.created = next;
}
else if(state == DELETED && this == view.deleted)
{
view.deleted = next;
}
if(next != null)
{
next.prev = prev;
}
if(prev != null)
{
prev.next = next;
}
next = null;
prev = null;
}
}
}