public void end(Xid xid, int flags) throws XAException { validateXid(xid); if (state != XA_STATE_STARTED) { throw new XAException("Invalid XAResource state"); } try { connection.setAutoCommit(originalAutoCommitMode); // real/phys. } catch (SQLException se) { throw new XAException(se.getMessage()); } state = XA_STATE_ENDED; }
@Test public void testLRC() throws Exception { TransactionManager tm = createTm(); assertFalse(tm.isLastResourceCommitSupported()); tm.begin(); tm.getTransaction().enlistResource(xares1); tm.getTransaction().enlistResource(xares2); tm.getTransaction().commit(); verify(xares1).start(any(Xid.class), anyInt()); verify(xares2).start(any(Xid.class), anyInt()); verify(xares1).prepare(any(Xid.class)); verify(xares2).prepare(any(Xid.class)); verify(xares1).commit(any(Xid.class), anyBoolean()); verify(xares2).commit(any(Xid.class), anyBoolean()); verify(xares1).end(any(Xid.class), anyInt()); verify(xares2).end(any(Xid.class), anyInt()); }
private void testXARecover(CloudSpannerXAConnection xaConnection) throws SQLException, XAException { log.info("Started XA recover transaction test"); testXATransaction(xaConnection, CommitMode.None); Xid[] xids = xaConnection.recover(XAResource.TMSTARTRSCAN); Assert.assertEquals(1, xids.length); xaConnection.commit(xids[0], false); boolean found = false; try (ResultSet rs = xaConnection.getConnection().createStatement() .executeQuery("select * from test where id=1000000")) { if (rs.next()) found = true; } Assert.assertTrue(found); log.info("Finished XA recover transaction test"); }
/** * * @throws XAException if the given Xid is the not the Xid of the current * transaction for this XAResource object. * @param xid Xid */ private void validateXid(Xid xid) throws XAException { if (xid == null) { throw new XAException("Null Xid"); } if (this.xid == null) { throw new XAException( "There is no live transaction for this XAResource"); } if (!xid.equals(this.xid)) { throw new XAException( "Given Xid is not that associated with this XAResource object"); } }
@Override public void start(Xid xid, int flags) throws XAException { if ( currentXid == null ) { if ( flags != TMNOFLAGS ) { throw new XAException( "Starting resource with wrong flags" ); } try { connection.transactionStart(); } catch ( Exception t ) { throw new XAException( "Error trying to start local transaction: " + t.getMessage() ); } currentXid = xid; } else { if ( flags != TMJOIN && flags != TMRESUME ) { throw new XAException( XAException.XAER_DUPID ); } } }
/** * The XAResource API spec indicates implies that this is only for 2-phase * transactions. I guess that one-phase transactions need to call rollback() * to abort. I think we want this JDBCXAResource instance to be * garbage-collectable after (a) this method is called, and (b) the tx * manager releases its handle to it. * * @see javax.transaction.xa.XAResource#forget(Xid) * @param xid Xid * @throws XAException */ public void forget(Xid xid) throws XAException { /** * Should this method not attempt to clean up the aborted * transaction by rolling back or something? Maybe the * tx manager will already have called rollback() if * it were necessary? */ validateXid(xid); if (state != XA_STATE_PREPARED) { throw new XAException( "Attempted to forget a XAResource that " + "is not in a heuristically completed state"); } dispose(); state = XA_STATE_INITIAL; }
/** * @throws XAException if the given Xid is the not the Xid of the * current transaction for this XAResource object. */ private void validateXid(Xid xid) throws XAException { if (xid == null) { throw new XAException("Null Xid"); } if (this.xid == null) { throw new XAException( "There is no live transaction for this XAResource"); } if (!xid.equals(this.xid)) { throw new XAException( "Given Xid is not that associated with this XAResource object"); } }
/** * Per the JDBC 3.0 spec, this commits the transaction for the * specified Xid, not necessarily for the transaction associated * with this XAResource object. */ public void commit(Xid xid, boolean onePhase) throws XAException { // Comment out following debug statement before public release: System.err.println("Performing a " + (onePhase ? "1-phase" : "2-phase") + " commit on " + xid); JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException("The XADataSource has no such Xid: " + xid); } resource.commitThis(onePhase); }
/** * The XAResource API spec indicates implies that this is only for * 2-phase transactions. * I guess that one-phase transactions need to call rollback() to abort. * * I think we want this JDBCXAResource instance to be garbage-collectable * after (a) this method is called, and (b) the tx manager releases its * handle to it. * * @see javax.transaction.xa.XAResource#forget(Xid) */ public void forget(Xid xid) throws XAException { /** * Should this method not attempt to clean up the aborted * transaction by rolling back or something? Maybe the * tx manager will already have called rollback() if * it were necessasry? */ validateXid(xid); if (state != XA_STATE_PREPARED) { throw new XAException( "Attempted to forget a XAResource that " + "is not in a heuristically completed state"); } dispose(); state = XA_STATE_INITIAL; }
/** * Retrieves a randomly generated JDBCXID. * * The newly generated object is based on the local IP address, the given * <tt>threadId</tt> and a randomly generated number using the current time * in milliseconds as the random seed. * * Note that java.util.Random is used, not java.security.SecureRandom. * * @param threadId can be a real thread id or just some convenient * tracking value. * * @return a randomly generated JDBCXID */ public static Xid getUniqueXid(final int threadId) { final Random random = new Random(System.currentTimeMillis()); // int txnSequenceNumberValue = nextTxnSequenceNumber(); int threadIdValue = threadId; int randomValue = random.nextInt(); // byte[] globalTransactionId = new byte[MAXGTRIDSIZE]; byte[] branchQualifier = new byte[MAXBQUALSIZE]; byte[] localIp = getLocalIp(); System.arraycopy(localIp, 0, globalTransactionId, 0, 4); System.arraycopy(localIp, 0, branchQualifier, 0, 4); // Bytes 4 -> 7 - unique transaction id. // Bytes 8 ->11 - thread id. // Bytes 12->15 - random. for (int i = 0; i <= 3; i++) { globalTransactionId[i + 4] = (byte) (txnSequenceNumberValue % 0x100); branchQualifier[i + 4] = (byte) (txnSequenceNumberValue % 0x100); txnSequenceNumberValue >>= 8; globalTransactionId[i + 8] = (byte) (threadIdValue % 0x100); branchQualifier[i + 8] = (byte) (threadIdValue % 0x100); threadIdValue >>= 8; globalTransactionId[i + 12] = (byte) (randomValue % 0x100); branchQualifier[i + 12] = (byte) (randomValue % 0x100); randomValue >>= 8; } return new JDBCXID(UXID_FORMAT_ID, globalTransactionId, branchQualifier); }
private synchronized void switchToXid(Xid xid) throws XAException { if (xid == null) { throw new XAException(); } try { if (!xid.equals(this.currentXid)) { XAConnection toSwitchTo = findConnectionForXid(this.underlyingConnection, xid); this.currentXAConnection = toSwitchTo; this.currentXid = xid; this.currentXAResource = toSwitchTo.getXAResource(); } } catch (SQLException sqlEx) { throw new XAException(); } }
/** * Tests fix for BUG#69506 - XAER_DUPID error code is not returned when a duplicate XID is offered in Java. * * @throws Exception * if the test fails. */ public void testBug69506() throws Exception { MysqlXADataSource dataSource = new MysqlXADataSource(); dataSource.setUrl(dbUrl); XAConnection testXAConn1 = dataSource.getXAConnection(); XAConnection testXAConn2 = dataSource.getXAConnection(); Xid duplicateXID = new MysqlXid("1".getBytes(), "1".getBytes(), 1); testXAConn1.getXAResource().start(duplicateXID, 0); try { testXAConn2.getXAResource().start(duplicateXID, 0); fail("XAException was expected."); } catch (XAException e) { assertEquals("Wrong error code retured for duplicated XID.", XAException.XAER_DUPID, e.errorCode); } }
/** * 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); }
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()); }
/** * @param s * The string to convert * @return recovered xid, or null if s does not represent a valid xid * encoded by the driver. */ public static Xid stringToXid(String s) { RecoveredXid xid = new RecoveredXid(); int a = s.indexOf("_"); int b = s.lastIndexOf("_"); if (a == b) { // this also catches the case a == b == -1. return null; } try { xid.formatId = Integer.parseInt(s.substring(0, a)); xid.globalTransactionId = Base64.decode(s.substring(a + 1, b)); xid.branchQualifier = Base64.decode(s.substring(b + 1)); if (xid.globalTransactionId == null || xid.branchQualifier == null) { return null; } } catch (Exception ex) { return null; // Doesn't seem to be an xid generated by this driver. } return xid; }
@Test public void testStart() throws SQLException, XAException { CloudSpannerXAConnection subject = createSubject(); ICloudSpannerConnection connection = subject.getConnection(); assertTrue(connection.getAutoCommit()); Xid xid = createXid(); subject.start(xid, XAResource.TMNOFLAGS); assertNotNull(connection); assertFalse(connection.getAutoCommit()); thrown.expect(CloudSpannerSQLException.class); connection.commit(); }
@Test public void testEnd() throws SQLException, XAException { CloudSpannerXAConnection subject = createSubject(); Xid xid = createXid(); subject.start(xid, XAResource.TMNOFLAGS); subject.end(xid, XAResource.TMSUCCESS); }
@Test public void testCommitTwoPhase() throws SQLException, XAException { CloudSpannerXAConnection subject = createSubject(); Xid xid = createXid(); subject.start(xid, XAResource.TMNOFLAGS); subject.end(xid, XAResource.TMSUCCESS); subject.prepare(xid); subject.commit(xid, false); }
private void testXATransaction(CloudSpannerXAConnection xaConnection, CommitMode mode) throws SQLException, XAException { log.info("Starting XA simple transaction test"); Random rnd = new Random(); Connection connection = xaConnection.getConnection(); int id = rnd.nextInt(); Xid xid = RecoveredXid.stringToXid(String.valueOf(id) + "_Z3RyaWQ=_YnF1YWw="); xaConnection.start(xid, XAResource.TMNOFLAGS); String sql = "insert into test (id, uuid, active, amount, description, created_date, last_updated) values (?, ?, ?, ?, ?, ?, ?)"; PreparedStatement statement = connection.prepareStatement(sql); setParameterValues(statement, 1000000); statement.executeUpdate(); xaConnection.end(xid, XAResource.TMSUCCESS); xaConnection.prepare(xid); if (mode != CommitMode.None) { xaConnection.commit(xid, mode == CommitMode.OnePhase); } if (mode != CommitMode.None) { boolean found = false; try (ResultSet rs = connection.createStatement().executeQuery("select * from test where id=1000000")) { if (rs.next()) found = true; } Assert.assertTrue(found); } log.info("Finished XA simple transaction test"); }
private static synchronized XAConnection findConnectionForXid(Connection connectionToWrap, Xid xid) throws SQLException { // TODO: check for same GTRID, but different BQUALs...MySQL doesn't allow this yet // Note, we don't need to check for XIDs here, because MySQL itself will complain with a XAER_NOTA if need be. XAConnection conn = XIDS_TO_PHYSICAL_CONNECTIONS.get(xid); if (conn == null) { conn = new MysqlXAConnection(connectionToWrap, connectionToWrap.getLogXaCommands()); XIDS_TO_PHYSICAL_CONNECTIONS.put(xid, conn); } return conn; }
private Xid prepareDeleteRow(CloudSpannerXAConnection xaConnection) throws SQLException, XAException { Random rnd = new Random(); Connection connection = xaConnection.getConnection(); int id = rnd.nextInt(); Xid xid = RecoveredXid.stringToXid(String.valueOf(id) + "_Z3RyaWQ=_YnF1YWw="); xaConnection.start(xid, XAResource.TMNOFLAGS); String sql = "delete from test where id=1000000"; PreparedStatement statement = connection.prepareStatement(sql); statement.executeUpdate(); xaConnection.end(xid, XAResource.TMSUCCESS); xaConnection.prepare(xid); return xid; }
/** * Vote on whether to commit the global transaction. We assume Xid may be * different from this, as in commit() method. * * @throws XAException to vote negative. * @return commitType of XA_RDONLY or XA_OK. (Actually only XA_OK now). * @param xid Xid */ public int prepare(Xid xid) throws XAException { JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException("The XADataSource has no such Xid: " + xid); } return resource.prepareThis(); }
/** * Vote on whether to commit the global transaction. * * @throws XAException to vote negative. * @return commitType of XA_RDONLY or XA_OK. (Actually only XA_OK now). */ public int prepare(Xid xid) throws XAException { validateXid(xid); /** * @todo: This is where the real 2-phase work should be done to * determine if a commit done here would succeed or not. */ /** * @todo: May improve performance to return XA_RDONLY whenever * possible, but I don't know. * Could determine this by checking if DB instance is in RO mode, * or perhaps (with much difficulty) to determine if there have * been any modifications performed. */ if (state != XA_STATE_ENDED) { throw new XAException("Invalid XAResource state"); } // throw new XAException( // "Sorry. HSQLDB has not implemented 2-phase commits yet"); state = XA_STATE_PREPARED; return XA_OK; // As noted above, should check non-committed work. }
@Test public void testRecovery() throws Exception { CountDownLatch latch = new CountDownLatch(1); XADataSource xaDs = Mockito.mock(XADataSource.class); XAConnection xaCon = Mockito.mock(XAConnection.class); XAResource xaRes = Mockito.mock(XAResource.class); Connection con = Mockito.mock(Connection.class); Mockito.when(xaDs.getXAConnection()).thenReturn(xaCon); Mockito.when(xaCon.getXAResource()).thenReturn(xaRes); Mockito.when(xaCon.getConnection()).thenReturn(con); Mockito.when(xaRes.recover(anyInt())).thenAnswer(invocation -> { latch.countDown(); return new Xid[0]; }); Mockito.when(con.getAutoCommit()).thenReturn(true); Mockito.when(con.isValid(anyInt())).thenReturn(true); DataSource ds = ManagedDataSourceBuilder.builder() .dataSource(xaDs) .transactionManager(tm) .name("h2") .build(); assertNotNull(ds); assertTrue(latch.await(5, TimeUnit.SECONDS)); }
public void end(Xid xid, int flag) throws XAException { Map<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid); if (indexers == null) { if (suspendedIndexersInGlobalTx.containsKey(xid)) { throw new XAException("Trying to commit indexes for a suspended transaction."); } else { // nothing to do return; } } if (flag == XAResource.TMSUSPEND) { activeIndexersInGlobalTx.remove(xid); suspendedIndexersInGlobalTx.put(xid, indexers); } else if (flag == TMFAIL) { activeIndexersInGlobalTx.remove(xid); suspendedIndexersInGlobalTx.remove(xid); } else if (flag == TMSUCCESS) { activeIndexersInGlobalTx.remove(xid); } }
public void rollback(Xid xid) throws XAException { // TODO: What to do if all do not roll back? try { Map<StoreRef, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid); if (indexers == null) { if (suspendedIndexersInGlobalTx.containsKey(xid)) { throw new XAException("Trying to commit indexes for a suspended transaction."); } else { // nothing to do return; } } for (LuceneIndexer indexer : indexers.values()) { indexer.rollback(); } } finally { activeIndexersInGlobalTx.remove(xid); } }
/** * Per the JDBC 3.0 spec, this rolls back the transaction for the specified * Xid, not necessarily for the transaction associated with this XAResource * object. * * @param xid Xid * @throws XAException */ public void rollback(Xid xid) throws XAException { JDBCXAResource resource = xaDataSource.getResource(xid); if (resource == null) { throw new XAException( "The XADataSource has no such Xid in prepared state: " + xid); } resource.rollbackThis(); }
public void start(Xid xid, int flags) throws XAException { // Comment out following debug statement before public release: /* System.err.println("STARTING NEW Xid: " + xid); */ if (state != XA_STATE_INITIAL && state != XA_STATE_DISPOSED) { throw new XAException("Invalid XAResource state"); } if (xaDataSource == null) { throw new XAException( "JDBCXAResource has not been associated with a XADataSource"); } if (xid == null) { // This block asserts that all JDBCXAResources with state // >= XA_STATE_STARTED have a non-null xid. throw new XAException("Null Xid"); } try { originalAutoCommitMode = connection.getAutoCommit(); // real/phys. connection.setAutoCommit(false); // real/phys. } catch (SQLException se) { throw new XAException(se.toString()); } this.xid = xid; state = XA_STATE_STARTED; xaDataSource.addResource(this.xid, this); // N.b. The DataSource does not have this XAResource in its list // until right here. We can't tell DataSource before our start() // method, because we don't know our Xid before now. }
public void start(Xid xid, int arg1) throws XAException { switchToXid(xid); if (arg1 != XAResource.TMJOIN) { this.currentXAResource.start(xid, arg1); return; } // // Emulate join, by using resume on the same physical connection // this.currentXAResource.start(xid, XAResource.TMRESUME); }
public void commit(Xid xid, boolean flag) throws XAException { if (this.xid == null || !this.xid.equals(xid)) { throw new XAException("Invalid Xid"); } try { localTransaction.commit(); } catch (ResourceException e) { throw (XAException)new XAException().initCause(e); } finally { this.xid = null; } }