All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mysql.jdbc.jdbc2.optional.MysqlXAConnection Maven / Gradle / Ivy

There is a newer version: 8.0.33
Show newest version
/*
  Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/J is licensed under the terms of the GPLv2
  , like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FOSS License Exception
  .

  This program is free software; you can redistribute it and/or modify it under the terms
  of the GNU General Public License as published by the Free Software Foundation; version 2
  of the License.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along with this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA

 */

package com.mysql.jdbc.jdbc2.optional;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.mysql.jdbc.Messages;
import com.mysql.jdbc.StringUtils;
import com.mysql.jdbc.Util;
import com.mysql.jdbc.log.Log;

/*
 * XA BEGIN  [JOIN | RESUME] XA START TRANSACTION  [JOIN | RESUME] XA
 * COMMIT  [ONE PHASE] XA END  [SUSPEND [FOR MIGRATE]] XA PREPARE
 *  XA RECOVER XA ROLLBACK 
 */

/**
 * An object that provides support for distributed transactions. An XAConnection object may be enlisted in a distributed transaction by means of
 * an XAResource object. A transaction manager, usually part of a middle tier server, manages an XAConnection object through the
 * XAResource object.
 * 
 * 

* An application programmer does not use this interface directly; rather, it is used by a transaction manager working in the middle tier server. */ public class MysqlXAConnection extends MysqlPooledConnection implements XAConnection, XAResource { private static final int MAX_COMMAND_LENGTH = 300; private com.mysql.jdbc.Connection underlyingConnection; private final static Map MYSQL_ERROR_CODES_TO_XA_ERROR_CODES; private Log log; protected boolean logXaCommands; static { HashMap temp = new HashMap(); temp.put(1397, XAException.XAER_NOTA); temp.put(1398, XAException.XAER_INVAL); temp.put(1399, XAException.XAER_RMFAIL); temp.put(1400, XAException.XAER_OUTSIDE); temp.put(1401, XAException.XAER_RMERR); temp.put(1402, XAException.XA_RBROLLBACK); temp.put(1440, XAException.XAER_DUPID); temp.put(1613, XAException.XA_RBTIMEOUT); temp.put(1614, XAException.XA_RBDEADLOCK); MYSQL_ERROR_CODES_TO_XA_ERROR_CODES = Collections.unmodifiableMap(temp); } private static final Constructor JDBC_4_XA_CONNECTION_WRAPPER_CTOR; static { if (Util.isJdbc4()) { try { JDBC_4_XA_CONNECTION_WRAPPER_CTOR = Class.forName("com.mysql.jdbc.jdbc2.optional.JDBC4MysqlXAConnection") .getConstructor(new Class[] { com.mysql.jdbc.Connection.class, Boolean.TYPE }); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else { JDBC_4_XA_CONNECTION_WRAPPER_CTOR = null; } } protected static MysqlXAConnection getInstance(com.mysql.jdbc.Connection mysqlConnection, boolean logXaCommands) throws SQLException { if (!Util.isJdbc4()) { return new MysqlXAConnection(mysqlConnection, logXaCommands); } return (MysqlXAConnection) Util.handleNewInstance(JDBC_4_XA_CONNECTION_WRAPPER_CTOR, new Object[] { mysqlConnection, Boolean.valueOf(logXaCommands) }, mysqlConnection.getExceptionInterceptor()); } /** * @param connection */ public MysqlXAConnection(com.mysql.jdbc.Connection connection, boolean logXaCommands) throws SQLException { super(connection); this.underlyingConnection = connection; this.log = connection.getLog(); this.logXaCommands = logXaCommands; } /** * Retrieves an XAResource object that the transaction * manager will use to manage this XAConnection object's * participation in a distributed transaction. * * @return the XAResource object * @exception SQLException * if a database access error occurs */ public XAResource getXAResource() throws SQLException { return this; } /** * Obtains the current transaction timeout value set for this XAResource * instance. If XAResource.setTransactionTimeout was not used prior to * invoking this method, the return value is the default timeout set for the * resource manager; otherwise, the value used in the previous * setTransactionTimeout call is returned. * * @return the transaction timeout value in seconds. * * @throws XAException * An error has occurred. Possible exception values are * XAER_RMERR and XAER_RMFAIL. */ public int getTransactionTimeout() throws XAException { return 0; } /** * Sets the current transaction timeout value for this XAResource instance. * Once set, this timeout value is effective until setTransactionTimeout is * invoked again with a different value. * * To reset the timeout value to the default value used by the resource * manager, set the value to zero. If the timeout operation is performed * successfully, the method returns true; otherwise false. * * If a resource manager does not support explicitly setting the transaction * timeout value, this method returns false. * * @parameter seconds The transaction timeout value in seconds. * * @return true if the transaction timeout value is set successfully; * otherwise false. * * @throws XAException * An error has occurred. Possible exception values are * XAER_RMERR, XAER_RMFAIL, or XAER_INVAL. */ public boolean setTransactionTimeout(int arg0) throws XAException { return false; } /** * This method is called to determine if the resource manager instance * represented by the target object is the same as the resouce manager * instance represented by the parameter xares. * * @parameter xares An XAResource object whose resource manager instance is * to be compared with the resource manager instance of the * target object. * * @return true if it's the same RM instance; otherwise false. * * @throws XAException * An error has occurred. Possible exception values are * XAER_RMERR and XAER_RMFAIL. */ public boolean isSameRM(XAResource xares) throws XAException { if (xares instanceof MysqlXAConnection) { return this.underlyingConnection.isSameResource(((MysqlXAConnection) xares).underlyingConnection); } return false; } /** * This method is called to obtain a list of prepared transaction branches * from a resource manager. The transaction manager calls this method during * recovery to obtain the list of transaction branches that are currently in * prepared or heuristically completed states. * * The flag parameter indicates where the recover scan should start or end, * or start and end. This method may be invoked one or more times during a * recovery scan. The resource manager maintains a cursor which marks the * current position of the prepared or heuristically completed transaction list. * Each invocation of the recover method moves the cursor passed the set of Xids * that are returned. * * Two consecutive invocation of this method that starts from the * beginning of the list must return the same list of transaction branches * unless one of the following takes place: * * - the transaction manager invokes the commit, forget, prepare, or rollback method for that resource * manager, between the two consecutive invocation of the recovery scan. * * - the resource manager heuristically completes some transaction branches * between the two invocation of the recovery scan. * * @param flag * One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS must be * used when no other flags are set in the parameter. * * @returns The resource manager returns zero or more XIDs of the * transaction branches that are currently in a prepared or * heuristically completed state. If an error occurs during the * operation, the resource manager should throw the appropriate * XAException. * * @throws XAException * An error has occurred. Possible values are XAER_RMERR, * XAER_RMFAIL, XAER_INVAL, and XAER_PROTO. */ public Xid[] recover(int flag) throws XAException { return recover(this.underlyingConnection, flag); } protected static Xid[] recover(Connection c, int flag) throws XAException { /* * The XA RECOVER statement returns information for those XA transactions on the MySQL server that are in the PREPARED state. (See Section 13.4.7.2, ???XA * Transaction States???.) The output includes a row for each such XA transaction on the server, regardless of which client started it. * * XA RECOVER output rows look like this (for an example xid value consisting of the parts 'abc', 'def', and 7): * * mysql> XA RECOVER; * +----------+--------------+--------------+--------+ * | formatID | gtrid_length | bqual_length | data | * +----------+--------------+--------------+--------+ * | 7 | 3 | 3 | abcdef | * +----------+--------------+--------------+--------+ * * The output columns have the following meanings: * * formatID is the formatID part of the transaction xid * gtrid_length is the length in bytes of the gtrid part of the xid * bqual_length is the length in bytes of the bqual part of the xid * data is the concatenation of the gtrid and bqual parts of the xid */ boolean startRscan = ((flag & TMSTARTRSCAN) > 0); boolean endRscan = ((flag & TMENDRSCAN) > 0); if (!startRscan && !endRscan && flag != TMNOFLAGS) { throw new MysqlXAException(XAException.XAER_INVAL, Messages.getString("MysqlXAConnection.001"), null); } // // We return all recovered XIDs at once, so if not TMSTARTRSCAN, return no new XIDs // // We don't attempt to maintain state to check for TMNOFLAGS "outside" of a scan // if (!startRscan) { return new Xid[0]; } ResultSet rs = null; Statement stmt = null; List recoveredXidList = new ArrayList(); try { // TODO: Cache this for lifetime of XAConnection stmt = c.createStatement(); rs = stmt.executeQuery("XA RECOVER"); while (rs.next()) { final int formatId = rs.getInt(1); int gtridLength = rs.getInt(2); int bqualLength = rs.getInt(3); byte[] gtridAndBqual = rs.getBytes(4); final byte[] gtrid = new byte[gtridLength]; final byte[] bqual = new byte[bqualLength]; if (gtridAndBqual.length != (gtridLength + bqualLength)) { throw new MysqlXAException(XAException.XA_RBPROTO, Messages.getString("MysqlXAConnection.002"), null); } System.arraycopy(gtridAndBqual, 0, gtrid, 0, gtridLength); System.arraycopy(gtridAndBqual, gtridLength, bqual, 0, bqualLength); recoveredXidList.add(new MysqlXid(gtrid, bqual, formatId)); } } catch (SQLException sqlEx) { throw mapXAExceptionFromSQLException(sqlEx); } finally { if (rs != null) { try { rs.close(); } catch (SQLException sqlEx) { throw mapXAExceptionFromSQLException(sqlEx); } } if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { throw mapXAExceptionFromSQLException(sqlEx); } } } int numXids = recoveredXidList.size(); Xid[] asXids = new Xid[numXids]; Object[] asObjects = recoveredXidList.toArray(); for (int i = 0; i < numXids; i++) { asXids[i] = (Xid) asObjects[i]; } return asXids; } /** * Asks the resource manager to prepare for a transaction commit of the * transaction specified in xid. * * @parameter xid A global transaction identifier. * * @returns A value indicating the resource manager's vote on the outcome of * the transaction. * * The possible values are: XA_RDONLY or XA_OK. If the resource manager * wants to roll back the transaction, it should do so by raising an * appropriate XAException in the prepare method. * * @throws XAException * An error has occurred. Possible exception values are: XA_RB*, * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or * XAER_PROTO. */ public int prepare(Xid xid) throws XAException { StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH); commandBuf.append("XA PREPARE "); appendXid(commandBuf, xid); dispatchCommand(commandBuf.toString()); return XA_OK; // TODO: Check for read-only } /** * Tells the resource manager to forget about a heuristically completed * transaction branch. * * @parameter xid A global transaction identifier. * * @throws XAException * An error has occurred. Possible exception values are * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or * XAER_PROTO. */ public void forget(Xid xid) throws XAException { // mysql doesn't support this } /** * Informs the resource manager to roll back work done on behalf of a * transaction branch. * * @parameter xid A global transaction identifier. * * @throws XAException * An error has occurred. Possible XAExceptions are XA_HEURHAZ, * XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL, * XAER_NOTA, XAER_INVAL, or XAER_PROTO. * * If the transaction branch is already marked rollback-only the resource * manager may throw one of the XA_RB* exceptions. * * Upon return, the resource manager has rolled back the branch's work and * has released all held resources. */ public void rollback(Xid xid) throws XAException { StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH); commandBuf.append("XA ROLLBACK "); appendXid(commandBuf, xid); try { dispatchCommand(commandBuf.toString()); } finally { this.underlyingConnection.setInGlobalTx(false); } } /** * Ends the work performed on behalf of a transaction branch. * * The resource manager disassociates the XA resource from the transaction * branch specified and lets the transaction complete. * * If TMSUSPEND is specified in the flags, the transaction branch is * temporarily suspended in an incomplete state. The transaction context is * in a suspended state and must be resumed via the start method with * TMRESUME specified. * * If TMFAIL is specified, the portion of work has failed. The resource * manager may mark the transaction as rollback-only * * If TMSUCCESS is specified, the portion of work has completed * successfully. * * @parameter xid A global transaction identifier that is the same as the * identifier used previously in the start method. * * @parameter flags One of TMSUCCESS, TMFAIL, or TMSUSPEND. * * @throws XAException * - * An error has occurred. Possible XAException values are * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, XAER_PROTO, * or XA_RB*. */ public void end(Xid xid, int flags) throws XAException { StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH); commandBuf.append("XA END "); appendXid(commandBuf, xid); switch (flags) { case TMSUCCESS: break; // no-op case TMSUSPEND: commandBuf.append(" SUSPEND"); break; case TMFAIL: break; // no-op default: throw new XAException(XAException.XAER_INVAL); } dispatchCommand(commandBuf.toString()); } /** * Starts work on behalf of a transaction branch specified in xid. * * If TMJOIN is specified, the start applies to joining a transaction * previously seen by the resource manager. * * If TMRESUME is specified, the start applies to resuming a suspended * transaction specified in the parameter xid. * * If neither TMJOIN nor TMRESUME is specified and the transaction specified * by xid has previously been seen by the resource manager, the resource * manager throws the XAException exception with XAER_DUPID error code. * * @parameter xid A global transaction identifier to be associated with the * resource. * * @parameter flags One of TMNOFLAGS, TMJOIN, or TMRESUME. * * @throws XAException * An error has occurred. Possible exceptions are XA_RB*, * XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, * XAER_INVAL, or XAER_PROTO. */ public void start(Xid xid, int flags) throws XAException { StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH); commandBuf.append("XA START "); appendXid(commandBuf, xid); switch (flags) { case TMJOIN: commandBuf.append(" JOIN"); break; case TMRESUME: commandBuf.append(" RESUME"); break; case TMNOFLAGS: // no-op break; default: throw new XAException(XAException.XAER_INVAL); } dispatchCommand(commandBuf.toString()); this.underlyingConnection.setInGlobalTx(true); } /** * Commits the global transaction specified by xid. * * @parameter xid A global transaction identifier * @parameter onePhase - If true, the resource manager should use a * one-phase commit protocol to commit the work done on behalf of * xid. * * @throws XAException * An error has occurred. Possible XAExceptions are XA_HEURHAZ, * XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL, * XAER_NOTA, XAER_INVAL, or XAER_PROTO. * * If the resource manager did not commit the transaction and the parameter * onePhase is set to true, the resource manager may throw one of the XA_RB* * exceptions. * * Upon return, the resource manager has rolled back the branch's work and * has released all held resources. */ public void commit(Xid xid, boolean onePhase) throws XAException { StringBuilder commandBuf = new StringBuilder(MAX_COMMAND_LENGTH); commandBuf.append("XA COMMIT "); appendXid(commandBuf, xid); if (onePhase) { commandBuf.append(" ONE PHASE"); } try { dispatchCommand(commandBuf.toString()); } finally { this.underlyingConnection.setInGlobalTx(false); } } private ResultSet dispatchCommand(String command) throws XAException { Statement stmt = null; try { if (this.logXaCommands) { this.log.logDebug("Executing XA statement: " + command); } // TODO: Cache this for lifetime of XAConnection stmt = this.underlyingConnection.createStatement(); stmt.execute(command); ResultSet rs = stmt.getResultSet(); return rs; } catch (SQLException sqlEx) { throw mapXAExceptionFromSQLException(sqlEx); } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { } } } } protected static XAException mapXAExceptionFromSQLException(SQLException sqlEx) { Integer xaCode = MYSQL_ERROR_CODES_TO_XA_ERROR_CODES.get(sqlEx.getErrorCode()); if (xaCode != null) { return (XAException) new MysqlXAException(xaCode.intValue(), sqlEx.getMessage(), null).initCause(sqlEx); } return (XAException) new MysqlXAException(XAException.XAER_RMFAIL, Messages.getString("MysqlXAConnection.003"), null).initCause(sqlEx); } private static void appendXid(StringBuilder builder, Xid xid) { byte[] gtrid = xid.getGlobalTransactionId(); byte[] btrid = xid.getBranchQualifier(); if (gtrid != null) { StringUtils.appendAsHex(builder, gtrid); } builder.append(','); if (btrid != null) { StringUtils.appendAsHex(builder, btrid); } builder.append(','); StringUtils.appendAsHex(builder, xid.getFormatId()); } /* * (non-Javadoc) * * @see javax.sql.PooledConnection#getConnection() */ @Override public synchronized Connection getConnection() throws SQLException { Connection connToWrap = getConnection(false, true); return connToWrap; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy