blob: 340e68ca35d00c7280d15eeb015348717d2c9abf [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.sql.*;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.mariadb.jdbc.util.StringUtils;
/** MariaDB pool connection implementation */
public class MariaDbPoolConnection implements PooledConnection, XAConnection {
private final Connection connection;
private final List<ConnectionEventListener> connectionEventListeners;
private final List<StatementEventListener> statementEventListeners;
/**
* Constructor.
*
* @param connection connection to retrieve connection options
*/
public MariaDbPoolConnection(Connection connection) {
this.connection = connection;
this.connection.setPoolConnection(this);
statementEventListeners = new CopyOnWriteArrayList<>();
connectionEventListeners = new CopyOnWriteArrayList<>();
}
@Override
public Connection getConnection() {
return connection;
}
@Override
public void addConnectionEventListener(ConnectionEventListener listener) {
connectionEventListeners.add(listener);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener listener) {
connectionEventListeners.remove(listener);
}
@Override
public void addStatementEventListener(StatementEventListener listener) {
statementEventListeners.add(listener);
}
@Override
public void removeStatementEventListener(StatementEventListener listener) {
statementEventListeners.remove(listener);
}
/**
* Fire statement close event to registered listeners.
*
* @param statement closing statement
*/
public void fireStatementClosed(PreparedStatement statement) {
StatementEvent event = new StatementEvent(this, statement);
for (StatementEventListener listener : statementEventListeners) {
listener.statementClosed(event);
}
}
/**
* Fire statement error event to registered listeners.
*
* @param statement closing statement
* @param returnEx exception
*/
public void fireStatementErrorOccurred(PreparedStatement statement, SQLException returnEx) {
StatementEvent event = new StatementEvent(this, statement, returnEx);
for (StatementEventListener listener : statementEventListeners) {
listener.statementErrorOccurred(event);
}
}
/**
* Fire connection close event to registered listeners.
*
* @param event close connection event
*/
public void fireConnectionClosed(ConnectionEvent event) {
for (ConnectionEventListener listener : connectionEventListeners) {
listener.connectionClosed(event);
}
}
/**
* Fire connection error event to registered listeners.
*
* @param returnEx exception
*/
public void fireConnectionErrorOccurred(SQLException returnEx) {
ConnectionEvent event = new ConnectionEvent(this, returnEx);
for (ConnectionEventListener listener : connectionEventListeners) {
listener.connectionErrorOccurred(event);
}
}
/**
* Close underlying connection
*
* @throws SQLException if close fails
*/
@Override
public void close() throws SQLException {
fireConnectionClosed(new ConnectionEvent(this));
connection.setPoolConnection(null);
connection.close();
}
/**
* Create XID string
*
* @param xid xid value
* @return XID string
*/
public static String xidToString(Xid xid) {
return "0x"
+ StringUtils.byteArrayToHexString(xid.getGlobalTransactionId())
+ ",0x"
+ StringUtils.byteArrayToHexString(xid.getBranchQualifier())
+ ",0x"
+ Integer.toHexString(xid.getFormatId());
}
@Override
public XAResource getXAResource() {
return new MariaDbXAResource();
}
private class MariaDbXAResource implements XAResource {
private String flagsToString(int flags) {
switch (flags) {
case TMJOIN:
return "JOIN";
case TMONEPHASE:
return "ONE PHASE";
case TMRESUME:
return "RESUME";
case TMSUSPEND:
return "SUSPEND";
default:
return "";
}
}
private XAException mapXaException(SQLException sqle) {
int xaErrorCode;
switch (sqle.getErrorCode()) {
case 1397:
xaErrorCode = XAException.XAER_NOTA;
break;
case 1398:
xaErrorCode = XAException.XAER_INVAL;
break;
case 1399:
xaErrorCode = XAException.XAER_RMFAIL;
break;
case 1400:
xaErrorCode = XAException.XAER_OUTSIDE;
break;
case 1401:
xaErrorCode = XAException.XAER_RMERR;
break;
case 1402:
xaErrorCode = XAException.XA_RBROLLBACK;
break;
default:
xaErrorCode = 0;
break;
}
XAException xaException;
if (xaErrorCode != 0) {
xaException = new XAException(xaErrorCode);
} else {
xaException = new XAException(sqle.getMessage());
}
xaException.initCause(sqle);
return xaException;
}
private void execute(String command) throws XAException {
try {
connection.createStatement().execute(command);
} catch (SQLException sqle) {
throw mapXaException(sqle);
}
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
execute("XA COMMIT " + xidToString(xid) + ((onePhase) ? " ONE PHASE" : ""));
}
@Override
public void end(Xid xid, int flags) throws XAException {
if (flags != TMSUCCESS && flags != TMSUSPEND && flags != TMFAIL) {
throw new XAException(XAException.XAER_INVAL);
}
execute("XA END " + xidToString(xid) + " " + flagsToString(flags));
}
@Override
public void forget(Xid xid) {
// Not implemented by the server
}
@Override
public int getTransactionTimeout() {
// not implemented
return 0;
}
public Configuration getConf() {
return connection.getContext().getConf();
}
@Override
public boolean isSameRM(XAResource xaResource) {
if (xaResource instanceof MariaDbXAResource) {
MariaDbXAResource other = (MariaDbXAResource) xaResource;
return other.getConf().equals(this.getConf());
}
return false;
}
@Override
public int prepare(Xid xid) throws XAException {
execute("XA PREPARE " + xidToString(xid));
return XA_OK;
}
@Override
public Xid[] recover(int flags) throws XAException {
if (((flags & TMSTARTRSCAN) == 0) && ((flags & TMENDRSCAN) == 0) && (flags != TMNOFLAGS)) {
throw new XAException(XAException.XAER_INVAL);
}
if ((flags & TMSTARTRSCAN) == 0) {
return new MariaDbXid[0];
}
try {
ResultSet rs = connection.createStatement().executeQuery("XA RECOVER");
ArrayList<MariaDbXid> xidList = new ArrayList<>();
while (rs.next()) {
int formatId = rs.getInt(1);
int len1 = rs.getInt(2);
int len2 = rs.getInt(3);
byte[] arr = rs.getBytes(4);
byte[] globalTransactionId = new byte[len1];
byte[] branchQualifier = new byte[len2];
System.arraycopy(arr, 0, globalTransactionId, 0, len1);
System.arraycopy(arr, len1, branchQualifier, 0, len2);
xidList.add(new MariaDbXid(formatId, globalTransactionId, branchQualifier));
}
Xid[] xids = new Xid[xidList.size()];
xidList.toArray(xids);
return xids;
} catch (SQLException sqle) {
throw mapXaException(sqle);
}
}
@Override
public void rollback(Xid xid) throws XAException {
execute("XA ROLLBACK " + xidToString(xid));
}
@Override
public boolean setTransactionTimeout(int i) {
return false;
}
@Override
public void start(Xid xid, int flags) throws XAException {
if (flags != TMJOIN && flags != TMRESUME && flags != TMNOFLAGS) {
throw new XAException(XAException.XAER_INVAL);
}
execute("XA START " + xidToString(xid) + " " + flagsToString(flags));
}
}
}