blob: bc8ec2fbcae7df41c5cbd1a125dedbe147c6bef6 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.transaction.manager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import junit.framework.TestCase;
/**
* This is just a unit test for recovery, depending on proper behavior of the log(s) it uses.
*
* @version $Rev$ $Date$
*
* */
public class RecoveryTest extends TestCase {
MockLog mockLog = new MockLog();
protected TransactionManagerImpl txManager;
private final String RM1 = "rm1";
private final String RM2 = "rm2";
private final String RM3 = "rm3";
private int count = 0;
@Override
protected void setUp() throws Exception {
txManager = createTransactionManager();
}
protected TransactionManagerImpl createTransactionManager() throws XAException, InterruptedException {
return new TransactionManagerImpl(1, new XidFactoryImpl("hi".getBytes()), mockLog);
}
protected void prepareForReplay() throws Exception {
Thread.sleep(100);
txManager = createTransactionManager();
}
public void testCommittedRMToBeRecovered() throws Exception {
Xid[] xids = getXidArray(1);
// specifies an empty Xid array such that this XAResource has nothing
// to recover. This means that the presumed abort protocol has failed
// right after the commit of the RM and before the force-write of the
// committed log record.
MockXAResource xares1 = new MockXAResource(RM1);
MockTransactionInfo[] txInfos = makeTxInfos(xids);
addBranch(txInfos, xares1);
prepareLog(mockLog, txInfos);
prepareForReplay();
Recovery recovery = txManager.recovery;
assertTrue(!recovery.hasRecoveryErrors());
assertTrue(recovery.getExternalXids().isEmpty());
assertTrue(!recovery.localRecoveryComplete());
recovery.recoverResourceManager(xares1);
assertEquals(1, xares1.committed.size());
assertTrue(recovery.localRecoveryComplete());
}
public void test2ResOnlineAfterRecoveryStart() throws Exception {
Xid[] xids = getXidArray(3);
MockXAResource xares1 = new MockXAResource(RM1);
MockXAResource xares2 = new MockXAResource(RM2);
MockTransactionInfo[] txInfos = makeTxInfos(xids);
addBranch(txInfos, xares1);
addBranch(txInfos, xares2);
prepareLog(mockLog, txInfos);
prepareForReplay();
Recovery recovery = txManager.recovery;
assertTrue(!recovery.hasRecoveryErrors());
assertTrue(recovery.getExternalXids().isEmpty());
assertTrue(!recovery.localRecoveryComplete());
recovery.recoverResourceManager(xares1);
assertTrue(!recovery.localRecoveryComplete());
assertEquals(3, xares1.committed.size());
recovery.recoverResourceManager(xares2);
assertEquals(3, xares2.committed.size());
assertTrue(txManager.recovery.localRecoveryComplete());
}
public void testInvalidXid() throws Exception {
final Xid[] xids = new Xid[] { new Xid() {
@Override
public int getFormatId() {
return 0;
}
@Override
public byte[] getGlobalTransactionId() {
return null;
}
@Override
public byte[] getBranchQualifier() {
return new byte[0];
}
} };
final NamedXAResource xares = new NamedXAResource() {
@Override
public String getName() {
return RM1;
}
@Override
public void commit(Xid xid, boolean b) throws XAException {
}
@Override
public void end(Xid xid, int i) throws XAException {
}
@Override
public void forget(Xid xid) throws XAException {
}
@Override
public int getTransactionTimeout() throws XAException {
return 0;
}
@Override
public boolean isSameRM(XAResource xaResource) throws XAException {
return false;
}
@Override
public int prepare(Xid xid) throws XAException {
return 0;
}
@Override
public Xid[] recover(int i) throws XAException {
return xids;
}
@Override
public void rollback(Xid xid) throws XAException {
}
@Override
public boolean setTransactionTimeout(int i) throws XAException {
return false;
}
@Override
public void start(Xid xid, int i) throws XAException {
}
};
prepareForReplay();
Recovery recovery = txManager.recovery;
recovery.recoverResourceManager(xares);
}
private void addBranch(MockTransactionInfo[] txInfos, MockXAResource xaRes) throws XAException {
for (int i = 0; i < txInfos.length; i++) {
MockTransactionInfo txInfo = txInfos[i];
Xid xid = txManager.getXidFactory().createBranch(txInfo.globalXid, count++);
xaRes.start(xid, 0);
txInfo.branches.add(new TransactionBranchInfoImpl(xid, xaRes.getName()));
}
}
private MockTransactionInfo[] makeTxInfos(Xid[] xids) {
MockTransactionInfo[] txInfos = new MockTransactionInfo[xids.length];
for (int i = 0; i < xids.length; i++) {
Xid xid = xids[i];
txInfos[i] = new MockTransactionInfo(xid, new ArrayList<TransactionBranchInfo>());
}
return txInfos;
}
public void test3ResOnlineAfterRecoveryStart() throws Exception {
Xid[] xids12 = getXidArray(3);
List xids12List = Arrays.asList(xids12);
Xid[] xids13 = getXidArray(3);
List xids13List = Arrays.asList(xids13);
Xid[] xids23 = getXidArray(3);
List xids23List = Arrays.asList(xids23);
ArrayList tmp = new ArrayList();
tmp.addAll(xids12List);
tmp.addAll(xids13List);
Xid[] xids1 = (Xid[]) tmp.toArray(new Xid[6]);
tmp.clear();
tmp.addAll(xids12List);
tmp.addAll(xids23List);
Xid[] xids2 = (Xid[]) tmp.toArray(new Xid[6]);
tmp.clear();
tmp.addAll(xids13List);
tmp.addAll(xids23List);
Xid[] xids3 = (Xid[]) tmp.toArray(new Xid[6]);
MockXAResource xares1 = new MockXAResource(RM1);
MockXAResource xares2 = new MockXAResource(RM2);
MockXAResource xares3 = new MockXAResource(RM3);
MockTransactionInfo[] txInfos12 = makeTxInfos(xids12);
addBranch(txInfos12, xares1);
addBranch(txInfos12, xares2);
prepareLog(mockLog, txInfos12);
MockTransactionInfo[] txInfos13 = makeTxInfos(xids13);
addBranch(txInfos13, xares1);
addBranch(txInfos13, xares3);
prepareLog(mockLog, txInfos13);
MockTransactionInfo[] txInfos23 = makeTxInfos(xids23);
addBranch(txInfos23, xares2);
addBranch(txInfos23, xares3);
prepareLog(mockLog, txInfos23);
prepareForReplay();
Recovery recovery = txManager.recovery;
assertTrue(!recovery.hasRecoveryErrors());
assertTrue(recovery.getExternalXids().isEmpty());
assertEquals(9, recovery.localUnrecoveredCount());
recovery.recoverResourceManager(xares1);
assertEquals(9, recovery.localUnrecoveredCount());
assertEquals(6, xares1.committed.size());
recovery.recoverResourceManager(xares2);
assertEquals(6, recovery.localUnrecoveredCount());
assertEquals(6, xares2.committed.size());
recovery.recoverResourceManager(xares3);
assertEquals(0, recovery.localUnrecoveredCount());
assertEquals(6, xares3.committed.size());
}
private void prepareLog(TransactionLog txLog, MockTransactionInfo[] txInfos) throws LogException {
for (int i = 0; i < txInfos.length; i++) {
MockTransactionInfo txInfo = txInfos[i];
txLog.prepare(txInfo.globalXid, txInfo.branches);
}
}
private Xid[] getXidArray(int i) {
Xid[] xids = new Xid[i];
for (int j = 0; j < xids.length; j++) {
xids[j] = txManager.getXidFactory().createXid();
}
return xids;
}
private static class MockXAResource implements NamedXAResource {
private final String name;
private final List<Xid> xids = new ArrayList<Xid>();
private final List<Xid> committed = new ArrayList<Xid>();
private final List<Xid> rolledBack = new ArrayList<Xid>();
public MockXAResource(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void commit(Xid xid, boolean onePhase) throws XAException {
committed.add(xid);
}
public void end(Xid xid, int flags) throws XAException {
}
public void forget(Xid xid) throws XAException {
}
public int getTransactionTimeout() throws XAException {
return 0;
}
public boolean isSameRM(XAResource xaResource) throws XAException {
return false;
}
public int prepare(Xid xid) throws XAException {
return 0;
}
public Xid[] recover(int flag) throws XAException {
return xids.toArray(new Xid[xids.size()]);
}
public void rollback(Xid xid) throws XAException {
rolledBack.add(xid);
}
public boolean setTransactionTimeout(int seconds) throws XAException {
return false;
}
public void start(Xid xid, int flags) throws XAException {
xids.add(xid);
}
public List getCommitted() {
return committed;
}
public List getRolledBack() {
return rolledBack;
}
}
private static class MockTransactionInfo {
private Xid globalXid;
private List<TransactionBranchInfo> branches;
public MockTransactionInfo(Xid globalXid, List<TransactionBranchInfo> branches) {
this.globalXid = globalXid;
this.branches = branches;
}
}
}