/**
* Copyright (C) 2000-2016 Atomikos <info@atomikos.com>
*
* LICENSE CONDITIONS
*
* See http://www.atomikos.com/Main/WhichLicenseApplies for details.
*/
package com.atomikos.recovery.xa;
import java.util.HashSet;
import java.util.Set;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.atomikos.datasource.xa.XID;
import com.atomikos.recovery.LogException;
public class XaResourceRecoveryManagerTestJUnit {
private static final String XARESOURCE_QUALIFIER = "XARESOURCE_QUALIFIER";
// SUT
XaResourceRecoveryManager xaResourceRecoveryManager;
private XAResource xaResource;
private XaRecoveryLog log;
@Before
public void initMocks() {
xaResource = Mockito.mock(XAResource.class);
log = Mockito.mock(XaRecoveryLog.class);
XaResourceRecoveryManager.installXaResourceRecoveryManager(log, XARESOURCE_QUALIFIER);
xaResourceRecoveryManager = XaResourceRecoveryManager.getInstance();
}
@Test
public void irrelevantResourceXidsAreIgnored() throws XAException {
XID xid = createIrrelevantXid();
givenXaResourceWithPreparedXid(xid);
whenRecovered();
thenNotTerminatedInXaResource(xid);
thenNotTerminatedInLog(xid);
}
private XID createIrrelevantXid() {
XID xid = new XID("demo", "Irrelevant" + XARESOURCE_QUALIFIER);
return xid;
}
@Test
public void relevantResourceXidsNotFoundInLogAreRolledback() throws XAException {
Xid xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
whenRecovered();
thenRolledBackInXaResource(xid);
}
@Test
public void preparingRelevantXidsAreRolledbackAfterExpiry() throws XAException, InterruptedException {
XID xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
givenExpiredPreparingXidInLog(xid);
whenRecovered();
thenRolledBackInXaResource(xid);
thenTerminatedInLog(xid);
}
@Test
public void preparingRelevantXidsAreNotRolledbackBeforeExpiryToMinimizeInterferenceWithOltpCommit()
throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
givenUnexpiredPreparingXidInLog(xid);
whenRecovered();
thenNotRolledbackInXaResource(xid);
thenNotTerminatedInLog(xid);
}
@Test
public void preparingRelevantXidsAreNotRolledbackAfterConcurrentOltpCommit() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
givenExpiredPreparingXidInLog(xid);
givenIntermediateCommitByOltp(xid);
whenRecovered();
thenNotRolledbackInXaResource(xid);
thenNotTerminatedInLog(xid);
}
@Test
public void committingRelevantXidsAreForgottenAfterHeuristicRollbackByResource() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithHeuristicallyRolledbackXid(xid);
givenExpiredCommittingXidInLog(xid);
whenRecovered();
thenForgottenInXaResource(xid);
thenHeuristicRollbackReportedToLog(xid);
}
@Test
public void rollingbackRelevantXidsAreForgottenAfterHeuristicCommitByResource() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithHeuristicallyCommittedXid(xid);
givenExpiredPreparingXidInLog(xid);
whenRecovered();
thenForgottenInXaResource(xid);
thenHeuristicCommitReportedToLog(xid);
}
@Test
public void committingRelevantXidsAreForgottenAfterHeuristicHazardFromResource() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithHeuristicHazardXid(xid);
givenExpiredCommittingXidInLog(xid);
whenRecovered();
thenForgottenInXaResource(xid);
thenHeuristicHazardReportedToLog(xid);
}
@Test
public void rollingbackRelevantXidsAreForgottenAfterHeuristicHazardFromResource() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithHeuristicHazardXid(xid);
givenExpiredPreparingXidInLog(xid);
whenRecovered();
thenForgottenInXaResource(xid);
thenHeuristicHazardReportedToLog(xid);
}
@Test
public void rollingbackRelevantXidsAreForgottenAfterHeuristicMixedFromResource() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithHeuristicMixedXid(xid);
givenExpiredPreparingXidInLog(xid);
whenRecovered();
thenForgottenInXaResource(xid);
thenHeuristicMixedReportedToLog(xid);
}
@Test
public void concurrentIntermediateRollbackInResourceDoesNotThrow() throws XAException {
Xid xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
givenExpiredPreparingXidInLog(xid);
givenIntermediateRollbackByOltp(xid);
whenRecovered();
}
@Test
public void autoForgetHeuristicsCanBeDisabledByConfiguration() throws Exception {
XID xid = createRelevantXid();
givenAutoForgetDisabled();
givenXaResourceWithHeuristicMixedXid(xid);
givenExpiredPreparingXidInLog(xid);
whenRecovered();
thenNotForgottenInXaResource(xid);
thenHeuristicMixedReportedToLog(xid);
}
private void givenAutoForgetDisabled() {
xaResourceRecoveryManager.setAutoForgetHeuristicsOnRecovery(false);
}
private void givenIntermediateRollbackByOltp(Xid xid) throws XAException {
Mockito.doThrow(new XAException(XAException.XAER_NOTA)).when(xaResource).rollback(xid);
}
private void givenXaResourceWithHeuristicHazardXid(Xid xid) throws XAException {
Xid[] xids = new Xid[1];
xids[0] = xid;
Mockito.when(xaResource.recover(Mockito.anyInt())).thenReturn(xids);
Mockito.doThrow(new XAException(XAException.XA_HEURHAZ)).when(xaResource).rollback(xid);
Mockito.doThrow(new XAException(XAException.XA_HEURHAZ)).when(xaResource).commit(xid, false);
}
private void givenXaResourceWithHeuristicallyCommittedXid(Xid xid) throws XAException {
Xid[] xids = new Xid[1];
xids[0] = xid;
Mockito.when(xaResource.recover(Mockito.anyInt())).thenReturn(xids);
Mockito.doThrow(new XAException(XAException.XA_HEURCOM)).when(xaResource).rollback(xid);
}
private void givenXaResourceWithHeuristicMixedXid(Xid xid) throws XAException {
Xid[] xids = new Xid[1];
xids[0] = xid;
Mockito.when(xaResource.recover(Mockito.anyInt())).thenReturn(xids);
Mockito.doThrow(new XAException(XAException.XA_HEURMIX)).when(xaResource).rollback(xid);
Mockito.doThrow(new XAException(XAException.XA_HEURMIX)).when(xaResource).commit(xid, false);
}
private void givenXaResourceWithHeuristicallyRolledbackXid(Xid xid) throws XAException {
Xid[] xids = new Xid[1];
xids[0] = xid;
Mockito.when(xaResource.recover(Mockito.anyInt())).thenReturn(xids);
Mockito.doThrow(new XAException(XAException.XA_HEURRB)).when(xaResource).commit(xid, false);
}
private void thenForgottenInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.times(1)).forget(xid);
}
private void thenNotForgottenInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.times(0)).forget(xid);
}
private void thenNotTerminatedInLog(XID xid) {
Mockito.verify(log, Mockito.times(0)).terminated(xid);
}
@Test
public void committingRelevantXidsAreCommittedAfterExpiry() throws Exception {
XID xid = createRelevantXid();
givenXaResourceWithPreparedXid(xid);
givenExpiredCommittingXidInLog(xid);
whenRecovered();
thenCommittedInXaResource(xid);
thenTerminatedInLog(xid);
}
@Test
public void failuresOnXaResourceRecoverDoNotThrow() throws Exception {
givenXaResourceWithExceptionOnRecover();
whenRecovered();
}
private void givenXaResourceWithExceptionOnRecover() throws XAException {
Mockito.when(xaResource.recover(Mockito.anyInt())).thenThrow(new XAException(XAException.XAER_RMERR));
}
private void thenTerminatedInLog(XID xid) {
Mockito.verify(log, Mockito.times(1)).terminated(xid);
}
private void thenHeuristicHazardReportedToLog(XID xid) throws LogException {
Mockito.verify(log, Mockito.times(1)).terminatedWithHeuristicHazardByResource(xid);
}
private void thenHeuristicMixedReportedToLog(XID xid) throws LogException {
Mockito.verify(log, Mockito.times(1)).terminatedWithHeuristicMixedByResource(xid);
}
private void thenHeuristicCommitReportedToLog(XID xid) throws LogException {
Mockito.verify(log, Mockito.times(1)).terminatedWithHeuristicCommitByResource(xid);
}
private void thenHeuristicRollbackReportedToLog(XID xid) throws LogException {
Mockito.verify(log, Mockito.times(1)).terminatedWithHeuristicRollbackByResource(xid);
}
private void thenCommittedInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.times(1)).commit(xid, false);
}
private void givenUnexpiredPreparingXidInLog(XID xid) throws IllegalStateException, LogException {
Mockito.doThrow(new IllegalStateException()).when(log).presumedAborting(xid);
}
private void givenExpiredPreparingXidInLog(Xid xid) {
// do nothing: don't make log mock throw on presumedAborting
}
private void givenExpiredCommittingXidInLog(XID xid) throws LogException {
Set<XID> toReturn = new HashSet<XID>();
toReturn.add(xid);
Mockito.when(log.getExpiredCommittingXids()).thenReturn(toReturn);
}
private void givenIntermediateCommitByOltp(XID xid) throws IllegalStateException, LogException {
Mockito.doThrow(new IllegalStateException()).when(log).presumedAborting(xid);
}
private void thenRolledBackInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.times(1)).rollback(xid);
}
private void thenNotRolledbackInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.never()).rollback(xid);
}
private XID createRelevantXid() {
XID xid = new XID("demo", XARESOURCE_QUALIFIER);
return xid;
}
private void thenNotTerminatedInXaResource(Xid xid) throws XAException {
Mockito.verify(xaResource, Mockito.never()).commit(xid, false);
Mockito.verify(xaResource, Mockito.never()).rollback(xid);
}
private void whenRecovered() {
xaResourceRecoveryManager.recover(xaResource);
}
private void givenXaResourceWithPreparedXid(Xid xid) throws XAException {
Xid[] xids = new Xid[1];
xids[0] = xid;
Mockito.when(xaResource.recover(Mockito.anyInt())).thenReturn(xids);
}
}