/*
* (C) 2007-2012 Alibaba Group Holding Limited.
*
* 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.
* Authors:
* wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
*/
package com.taobao.metamorphosis.client.transaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.taobao.gecko.core.util.OpaqueGenerator;
import com.taobao.gecko.service.RemotingClient;
import com.taobao.gecko.service.exception.NotifyRemotingException;
import com.taobao.metamorphosis.exception.MetaClientException;
import com.taobao.metamorphosis.network.BooleanCommand;
import com.taobao.metamorphosis.network.HttpStatus;
import com.taobao.metamorphosis.network.TransactionCommand;
import com.taobao.metamorphosis.transaction.LocalTransactionId;
import com.taobao.metamorphosis.transaction.TransactionId;
import com.taobao.metamorphosis.transaction.TransactionInfo;
import com.taobao.metamorphosis.transaction.TransactionInfo.TransactionType;
import com.taobao.metamorphosis.transaction.XATransactionId;
import com.taobao.metamorphosis.utils.IdGenerator;
import com.taobao.metamorphosis.utils.LongSequenceGenerator;
public class TransactionContextUnitTest {
private static final String UNIQUE_QUALIFIER = XIDGenerator.UNIQUE_QUALIFIER;
private TransactionContext context;
private MockSession session;
private RemotingClient remotingClient;
private IdGenerator idGenerator;
private String sessionId;
private static final long DEFAULT_REQ_TIMEOUT = 5000L;
static class MockSession implements TransactionSession {
private final String sessionId;
private boolean removeCtx = false;
public MockSession(final String sessionId) {
super();
this.sessionId = sessionId;
}
@Override
public void removeContext(final TransactionContext ctx) {
this.removeCtx = true;
}
@Override
public String getSessionId() {
return this.sessionId;
}
}
@Before
public void setUp() {
this.idGenerator = new IdGenerator();
this.remotingClient = EasyMock.createMock(RemotingClient.class);
this.sessionId = this.idGenerator.generateId();
this.session = new MockSession(this.sessionId);
this.context =
new TransactionContext(this.remotingClient, null, this.session, new LongSequenceGenerator(), 0,
DEFAULT_REQ_TIMEOUT);
this.context.setUniqueQualifier(UNIQUE_QUALIFIER);
OpaqueGenerator.resetOpaque();
}
@After
public void tearDown() {
this.verify();
}
private void verify() {
EasyMock.verify(this.remotingClient);
}
private void replay() {
EasyMock.replay(this.remotingClient);
}
@Test
public void testLocalBeginCommit() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final TransactionId id = new LocalTransactionId(this.sessionId, 1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.COMMIT_ONE_PHASE),
null);
assertNull(this.context.getTransactionId());
assertFalse(this.context.isInTransaction());
OpaqueGenerator.resetOpaque();
this.replay();
this.context.begin();
final TransactionId xid = this.context.getTransactionId();
assertNotNull(xid);
assertTrue(xid.isLocalTransaction());
assertTrue(this.context.isInLocalTransaction());
assertFalse(this.context.isInXATransaction());
assertTrue(this.context.isInTransaction());
this.context.commit();
assertNull(this.context.getTransactionId());
assertFalse(this.context.isInTransaction());
}
@Test(expected = XAException.class)
public void testSetTxTimeoutInvalidValue() throws Exception {
this.replay();
this.context.setTransactionTimeout(-1);
}
@Test
public void testSetTxTimeout() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.context.setTransactionTimeout(3);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN, null, 3), null);
// this.mockSend(serverUrl, new TransactionInfo(id, this.sessionId,
// TransactionType.SET_TIMEOUT, 3));
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
}
@Test(expected = MetaClientException.class)
public void testBeginUnConnected() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, false);
this.context.setServerUrl(serverUrl);
this.replay();
this.context.begin();
}
@Test(expected = MetaClientException.class)
public void testCommitUnBeginTx() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.context.setServerUrl(serverUrl);
this.replay();
this.context.commit();
}
@Test(expected = MetaClientException.class)
public void testRollbackUnBeginTx() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.context.setServerUrl(serverUrl);
this.replay();
this.context.rollback();
}
@Test
public void testLocalBeginRollback() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final TransactionId id = new LocalTransactionId(this.sessionId, 1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.ROLLBACK), null);
assertNull(this.context.getTransactionId());
assertFalse(this.context.isInTransaction());
OpaqueGenerator.resetOpaque();
this.replay();
this.context.begin();
final TransactionId xid = this.context.getTransactionId();
assertNotNull(xid);
assertTrue(xid.isLocalTransaction());
assertTrue(this.context.isInLocalTransaction());
assertFalse(this.context.isInXATransaction());
assertTrue(this.context.isInTransaction());
this.context.rollback();
assertNull(this.context.getTransactionId());
assertFalse(this.context.isInTransaction());
}
@Test
public void testBeginXAWithServerUrl() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
assertNull(this.context.getTransactionId());
assertFalse(this.context.isInTransaction());
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
final TransactionId xid = this.context.getTransactionId();
assertNotNull(xid);
assertEquals(id, xid);
assertFalse(xid.isLocalTransaction());
assertFalse(this.context.isInLocalTransaction());
assertTrue(this.context.isInXATransaction());
assertTrue(this.context.isInTransaction());
}
@Test
public void testBeginXAEndPrepareCommit() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
this.mockSend(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.END));
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.PREPARE),
String.valueOf(XAResource.XA_OK));
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.COMMIT_TWO_PHASE),
null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
assertTrue(this.context.isInXATransaction());
this.context.end(id, XAResource.TMSUCCESS);
assertFalse(this.context.isInXATransaction());
this.context.prepare(id);
this.context.commit(id, false);
assertFalse(this.context.isInXATransaction());
assertTrue(this.session.removeCtx);
}
@Test(expected = XAException.class)
public void testBeginXAPrepare() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
assertTrue(this.context.isInXATransaction());
// û�е���end��ֱ��prepare
this.context.prepare(id);
fail();
}
@Test(expected = XAException.class)
public void testBeginXACommit() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
assertTrue(this.context.isInXATransaction());
// û�е���end��ֱ��commit
this.context.commit(id, false);
fail();
}
@Test
public void testBeginXARollback() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.ROLLBACK), null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
assertFalse(this.session.removeCtx);
this.context.rollback(id);
assertTrue(this.session.removeCtx);
}
@Test
public void testBeginXAEndPrepareRollback() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.BEGIN), null);
this.mockSend(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.END));
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.PREPARE),
String.valueOf(XAResource.XA_OK));
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.ROLLBACK), null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.start(id, XAResource.TMNOFLAGS);
assertFalse(this.session.removeCtx);
this.context.end(id, XAResource.TMSUCCESS);
this.context.prepare(id);
this.context.rollback(id);
assertTrue(this.session.removeCtx);
assertFalse(this.context.isInXATransaction());
}
@Test
public void testCommitUnBeginXA() throws Exception {
// ��������ģ�������recover����������
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.COMMIT_TWO_PHASE),
null);
OpaqueGenerator.resetOpaque();
this.replay();
this.context.commit(id, false);
}
@Test
public void testPrepareUnBeginXA() throws Exception {
// ��������ģ�������recover����������
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setServerUrl(serverUrl);
final XATransactionId id = XIDGenerator.createXID(1);
this.mockInvokeSuccess(serverUrl, new TransactionInfo(id, this.sessionId, TransactionType.PREPARE),
String.valueOf(XAResource.XA_OK));
OpaqueGenerator.resetOpaque();
this.replay();
this.context.prepare(id);
}
@Test
public void testRecover() throws Exception {
final String serverURL1 = "meta://localhost:8123";
final String serverURL2 = "meta://localhost:8124";
final XATransactionId id1 = XIDGenerator.createXID(1);
final XATransactionId id2 = XIDGenerator.createXID(2);
final XATransactionId id3 = XIDGenerator.createXID(3);
final XATransactionId id4 = XIDGenerator.createXID(4);
this.mockInvokeSuccess(serverURL1, new TransactionInfo(null, this.sessionId, TransactionType.RECOVER),
id2.getTransactionKey() + "\r\n" + id3.getTransactionKey());
this.mockInvokeSuccess(serverURL2, new TransactionInfo(null, this.sessionId, TransactionType.RECOVER),
id1.getTransactionKey() + "\r\n" + id4.getTransactionKey());
this.replay();
OpaqueGenerator.resetOpaque();
this.context.setXareresourceURLs(new String[] { serverURL1, serverURL2 });
final Xid[] xids = this.context.recover(XAResource.TMNOFLAGS);
assertEquals(4, xids.length);
this.assertContains(xids, id1);
this.assertContains(xids, id2);
this.assertContains(xids, id3);
this.assertContains(xids, id4);
}
@Test
public void testIsSameRM() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.context.setServerUrl(serverUrl);
final TransactionContext ctx2 =
new TransactionContext(this.remotingClient, null, this.session, new LongSequenceGenerator(), 0,
DEFAULT_REQ_TIMEOUT);
ctx2.setServerUrl(serverUrl);
assertTrue(this.context.isSameRM(ctx2));
this.replay();
}
@Test
public void testToXAException() {
final XAException e = new XAException();
e.errorCode = XAException.XA_HEURCOM;
assertSame(e, this.context.toXAException(e));
XAException xe = this.context.toXAException(new RuntimeException(e));
assertEquals(XAException.XA_HEURCOM, xe.errorCode);
xe = this.context.toXAException(new RuntimeException("test"));
assertEquals(XAException.XAER_RMFAIL, xe.errorCode);
assertEquals("test", xe.getCause().getMessage());
this.replay();
}
@Test
public void testRecoverBlank() throws Exception {
final String serverUrl = "meta://localhost:8123";
this.mockIsConnected(serverUrl, true);
this.context.setXareresourceURLs(new String[] { serverUrl });
this.mockInvokeSuccess(serverUrl, new TransactionInfo(null, this.sessionId, TransactionType.RECOVER), null);
this.replay();
OpaqueGenerator.resetOpaque();
final Xid[] xids = this.context.recover(XAResource.TMNOFLAGS);
assertEquals(0, xids.length);
}
private void assertContains(final Xid[] a, final Xid key) {
boolean contains = false;
for (final Xid e : a) {
if (e.equals(key)) {
contains = true;
break;
}
}
if (!contains) {
throw new AssertionError();
}
}
private void mockIsConnected(final String serverUrl, final boolean rt) {
EasyMock.expect(this.remotingClient.isConnected(serverUrl)).andReturn(rt).anyTimes();
}
private void mockInvokeSuccess(final String serverUrl, final TransactionInfo info, final String result)
throws InterruptedException, TimeoutException, NotifyRemotingException {
info.setUniqueQualifier(UNIQUE_QUALIFIER);
EasyMock.expect(
this.remotingClient.invokeToGroup(serverUrl, new TransactionCommand(info, OpaqueGenerator.getNextOpaque()),
DEFAULT_REQ_TIMEOUT, TimeUnit.MILLISECONDS)).andReturn(
new BooleanCommand(HttpStatus.Success, result, 0));
}
private void mockSend(final String serverUrl, final TransactionInfo info) throws NotifyRemotingException {
info.setUniqueQualifier(UNIQUE_QUALIFIER);
this.remotingClient.sendToGroup(serverUrl, new TransactionCommand(info, OpaqueGenerator.getNextOpaque()),
TransactionContext.END_XA_TX_LISTENER, 5000, TimeUnit.MILLISECONDS);
EasyMock.expectLastCall();
}
}