/*
* 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.as.cmp.jdbc2.schema;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import javax.transaction.Transaction;
import org.jboss.as.cmp.jdbc.JDBCUtil;
import org.jboss.as.cmp.jdbc.SQLUtil;
import org.jboss.as.cmp.jdbc.metadata.JDBCRelationMetaData;
import org.jboss.as.cmp.jdbc2.bridge.JDBCCMPFieldBridge2;
import org.jboss.as.cmp.jdbc2.bridge.JDBCCMRFieldBridge2;
import org.jboss.logging.Logger;
/**
* @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) {
this.schema = schema;
this.tableId = tableId;
this.leftField = leftField;
this.rightField = rightField;
JDBCRelationMetaData metadata = leftField.getMetaData().getRelationMetaData();
ds = leftField.getManager().getDataSource(metadata.getDataSourceName());
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;
}
}
}