/**
*
* Copyright 2004-2005 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.
*/
package org.apache.geronimo.connector.outbound.transactionlog;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.sql.DataSource;
import javax.transaction.xa.Xid;
import org.apache.geronimo.connector.outbound.ManagedConnectionFactoryWrapper;
import org.apache.geronimo.gbean.GBeanLifecycle;
import org.apache.geronimo.transaction.manager.LogException;
import org.apache.geronimo.transaction.manager.Recovery;
import org.apache.geronimo.transaction.manager.TransactionBranchInfo;
import org.apache.geronimo.transaction.manager.TransactionBranchInfoImpl;
import org.apache.geronimo.transaction.manager.TransactionLog;
import org.apache.geronimo.transaction.manager.XidFactory;
/**
* "Last Resource optimization" for single servers wishing to have valid xa transactions with
* a single 1-pc datasource. The database is used for the log, and the database work is
* committed when the log writes its prepare record.
*
* @version $Rev: 231369 $ $Date: 2005-08-10 19:42:16 -0600 (Wed, 10 Aug 2005) $
*/
public class JDBCLog implements TransactionLog, GBeanLifecycle {
private final static String INSERT_XID = "INSERT INTO TXLOG (SYSTEMID, FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME) VALUES (?, ?, ?, ?, ?)";
private final static String DELETE_XID = "DELETE FROM TXLOG WHERE SYSTEMID = ? AND FORMATID = ? AND GLOBALID = ? AND GLOBALBRANCHID = ?";
private final static String RECOVER = "SELECT FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME FROM TXLOG WHERE SYSTEMID = ? ORDER BY FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME";
private DataSource dataSource;
private final String systemId;
private final ManagedConnectionFactoryWrapper managedConnectionFactoryWrapper;
public JDBCLog(String systemId, ManagedConnectionFactoryWrapper managedConnectionFactoryWrapper) {
this.systemId = systemId;
this.managedConnectionFactoryWrapper = managedConnectionFactoryWrapper;
}
public JDBCLog(String systemId, DataSource dataSource) {
this.systemId = systemId;
this.managedConnectionFactoryWrapper = null;
this.dataSource = dataSource;
}
public void doStart() throws Exception {
dataSource = (DataSource) managedConnectionFactoryWrapper.$getResource();
}
public void doStop() throws Exception {
dataSource = null;
}
public void doFail() {
}
public void begin(Xid xid) throws LogException {
}
public Object prepare(Xid xid, List branches) throws LogException {
int formatId = xid.getFormatId();
byte[] globalTransactionId = xid.getGlobalTransactionId();
byte[] branchQualifier = xid.getBranchQualifier();
try {
Connection connection = dataSource.getConnection();
try {
PreparedStatement ps = connection.prepareStatement(INSERT_XID);
try {
for (Iterator iterator = branches.iterator(); iterator.hasNext();) {
TransactionBranchInfo branch = (TransactionBranchInfo) iterator.next();
ps.setString(0, systemId);
ps.setInt(1, formatId);
ps.setBytes(2, globalTransactionId);
ps.setBytes(3, branchQualifier);
ps.setBytes(4, branch.getBranchXid().getBranchQualifier());
ps.setString(5, branch.getResourceName());
ps.execute();
}
} finally {
ps.close();
}
if (!connection.getAutoCommit()) {
connection.commit();
}
} finally {
connection.close();
}
} catch (SQLException e) {
throw new LogException("Failure during prepare or commit", e);
}
return null;
}
public void commit(Xid xid, Object logMark) throws LogException {
try {
Connection connection = dataSource.getConnection();
try {
PreparedStatement ps = connection.prepareStatement(DELETE_XID);
try {
ps.setString(0, systemId);
ps.setInt(1, xid.getFormatId());
ps.setBytes(2, xid.getGlobalTransactionId());
ps.setBytes(3, xid.getBranchQualifier());
ps.execute();
} finally {
ps.close();
}
if (!connection.getAutoCommit()) {
connection.commit();
}
} finally {
connection.close();
}
} catch (SQLException e) {
throw new LogException("Failure during prepare or commit", e);
}
}
public void rollback(Xid xid, Object logMark) throws LogException {
throw new LogException("JDBCLog does not support rollback of prepared transactions. Use it only on servers that do not import transactions");
}
public Collection recover(XidFactory xidFactory) throws LogException {
try {
Connection connection = dataSource.getConnection();
try {
Collection recovered = new ArrayList();
PreparedStatement ps = connection.prepareStatement(RECOVER);
try {
ps.setString(0, systemId);
ResultSet rs = ps.executeQuery();
try {
Xid lastXid = null;
Xid currentXid = null;
Recovery.XidBranchesPair xidBranchesPair = null;
while (rs.next()) {
int formatId = rs.getInt(0);
byte[] globalId = rs.getBytes(1);
byte[] globalBranchId = rs.getBytes(2);
byte[] branchBranchId = rs.getBytes(3);
String name = rs.getString(4);
currentXid = xidFactory.recover(formatId, globalId, globalBranchId);
Xid branchXid = xidFactory.recover(formatId, globalId, branchBranchId);
if (!currentXid.equals(lastXid)) {
xidBranchesPair = new Recovery.XidBranchesPair(currentXid, null);
recovered.add(xidBranchesPair);
lastXid = currentXid;
}
xidBranchesPair.addBranch(new TransactionBranchInfoImpl(branchXid, name));
}
return recovered;
} finally {
rs.close();
}
} finally {
ps.close();
}
} finally {
connection.close();
}
} catch (SQLException e) {
throw new LogException("Recovery failure", e);
}
}
public String getXMLStats() {
return null;
}
public int getAverageForceTime() {
return 0;
}
public int getAverageBytesPerForce() {
return 0;
}
}