blob: 92c8def6dc3453c7069b9017a455275970b74393 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je;
import java.io.File;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.txn.PreparedTxn;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.txn.TxnManager;
/**
* An Environment that implements XAResource. If JE is used in an XA
* environment, this class should be used instead of Environment so that
* appropriate XA functions are available.
*/
public class XAEnvironment extends Environment implements XAResource {
private static final boolean DEBUG = false;
/**
* Create a database environment handle.
*
* @param envHome The database environment's home directory.
*
* @param configuration The database environment attributes. If null,
* default attributes are used.
*/
public XAEnvironment(File envHome, EnvironmentConfig configuration)
throws EnvironmentNotFoundException, EnvironmentLockedException {
super(envHome, configuration);
}
/**
* Used to get the Transaction object given an XA Xid.
* @hidden
* Internal use only.
*/
public Transaction getXATransaction(Xid xid) {
Txn ret = getXATransactionInternal(xid);
if (ret == null ||
ret instanceof PreparedTxn) {
return null;
}
/* Do we guarantee object identity for Transaction objects? */
return new Transaction(this, ret);
}
private Txn getXATransactionInternal(Xid xid) {
return getNonNullEnvImpl().getTxnManager().getTxnFromXid(xid);
}
/**
* Used to set the Transaction object for an XA Xid. Public for tests.
*
* @hidden
* Internal use only.
*/
public void setXATransaction(Xid xid, Transaction txn) {
getNonNullEnvImpl().getTxnManager().registerXATxn(
xid, txn.getTxn(), false);
}
/*
* XAResource methods.
*/
@Override
public void commit(Xid xid, boolean ignore /*onePhase*/)
throws XAException {
if (DEBUG) {
System.out.println("*** commit called " + xid + "/" + ignore);
}
if (xid == null) {
return;
}
try {
checkOpen();
Txn txn = getXATransactionInternal(xid);
if (txn == null) {
throw new XAException
("No transaction found for " + xid + " during commit.");
}
removeReferringHandle(new Transaction(this, txn));
if (txn.isOnlyAbortable()) {
throw new XAException(XAException.XA_RBROLLBACK);
}
txn.commit(xid);
} catch (DatabaseException DE) {
throwNewXAException(DE);
}
if (DEBUG) {
System.out.println("*** commit finished");
}
}
@Override
public void end(Xid xid, int flags)
throws XAException {
if (DEBUG) {
System.out.println("*** end called " + xid + "/" + flags);
}
/* flags - One of TMSUCCESS, TMFAIL, or TMSUSPEND. */
boolean tmFail = (flags & XAResource.TMFAIL) != 0;
boolean tmSuccess = (flags & XAResource.TMSUCCESS) != 0;
boolean tmSuspend = (flags & XAResource.TMSUSPEND) != 0;
if ((!tmFail && !tmSuccess && !tmSuspend) ||
(tmFail && tmSuccess) ||
((tmFail || tmSuccess) && tmSuspend)) {
throw new XAException(XAException.XAER_INVAL);
}
final EnvironmentImpl envImpl = getNonNullEnvImpl();
if (DEBUG) {
System.out.println
("Transaction for " + Thread.currentThread() + " is " +
envImpl.getTxnManager().getTxnForThread());
}
Transaction transaction = envImpl.getTxnManager().unsetTxnForThread();
if (transaction == null) {
transaction = getXATransaction(xid);
}
Txn txn = (transaction != null) ? transaction.getTxn() : null;
if (txn == null) {
throw new XAException(XAException.XAER_NOTA);
}
if (tmFail) {
/*
* Creating the XAFailureException will set the txn to abort-only.
* This exception stack trace will provide more "cause" information
* when it is wrapped in an exception that is thrown later, which
* occurs when an attempt is made to use the txn.
*/
new XAFailureException(txn);
}
if (tmSuspend) {
if (txn.isSuspended()) {
throw new XAException(XAException.XAER_PROTO);
}
txn.setSuspended(true);
}
/* For tmSuccess, do nothing further. */
}
@Override
public void forget(Xid xid)
throws XAException {
if (DEBUG) {
System.out.println("*** forget called");
}
throw new XAException(XAException.XAER_NOTA);
}
@Override
public boolean isSameRM(XAResource rm)
throws XAException {
if (DEBUG) {
System.out.println("*** isSameRM called");
}
EnvironmentImpl envImpl = null;
try {
envImpl = checkOpen();
} catch (DatabaseException DE) {
throwNewXAException(DE);
}
if (rm == null) {
return false;
}
if (!(rm instanceof XAEnvironment)) {
return false;
}
return envImpl ==
DbInternal.getNonNullEnvImpl((XAEnvironment) rm);
}
@Override
public int prepare(Xid xid)
throws XAException {
if (DEBUG) {
System.out.println("*** prepare called");
}
try {
checkOpen();
Transaction txn = getXATransaction(xid);
if (txn == null) {
throw new XAException
("No transaction found for " + xid + " during prepare.");
}
int ret = txn.getTxn().prepare(xid);
if (DEBUG) {
System.out.println("*** prepare returning " + ret);
}
/*
* If this transaction was R/O, then there were no writes. We'll
* commit it here because the user doesn't need to (and isn't
* allowed to either).
*/
if (ret == XAResource.XA_RDONLY) {
commit(xid, true);
}
return ret;
} catch (RuntimeException e) {
throwNewXAException(e);
}
return XAResource.XA_OK; // for compiler
}
@Override
public Xid[] recover(int flags)
throws XAException {
if (DEBUG) {
System.out.println("*** recover called");
}
/* flags - One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. */
boolean tmStartRScan = (flags & XAResource.TMSTARTRSCAN) != 0;
boolean tmEndRScan = (flags & XAResource.TMENDRSCAN) != 0;
if ((tmStartRScan && tmEndRScan) ||
(!tmStartRScan && !tmEndRScan && flags != TMNOFLAGS)) {
throw new XAException(XAException.XAER_INVAL);
}
/*
* We don't have to actually do anything with STARTRSCAN or ENDRSCAN
* since we return the whole set of Xid's to be recovered on each call.
*/
try {
final EnvironmentImpl envImpl = checkOpen();
if (DEBUG) {
System.out.println("*** recover returning1");
}
return envImpl.getTxnManager().XARecover();
} catch (DatabaseException DE) {
throwNewXAException(DE);
}
return null; // for compiler
}
@Override
public void rollback(Xid xid)
throws XAException {
if (DEBUG) {
System.out.println("*** rollback called");
}
try {
checkOpen();
Txn txn = getXATransactionInternal(xid);
if (txn == null) {
throw new XAException
("No transaction found for " + xid + " during rollback.");
}
removeReferringHandle(new Transaction(this, txn));
txn.abort(xid);
} catch (DatabaseException DE) {
throwNewXAException(DE);
}
if (DEBUG) {
System.out.println("*** rollback returning");
}
}
@Override
public int getTransactionTimeout()
throws XAException {
try {
return (int) ((getConfig().getTxnTimeout() + 999999L) / 1000000L);
} catch (Exception DE) {
throwNewXAException(DE);
}
return 0; // for compiler
}
@Override
public boolean setTransactionTimeout(int timeout) {
return false;
}
@Override
public void start(Xid xid, int flags)
throws XAException {
if (DEBUG) {
System.out.println("*** start called " + xid + "/" + flags);
}
boolean tmJoin = (flags & XAResource.TMJOIN) != 0;
boolean tmResume = (flags & XAResource.TMRESUME) != 0;
/* Check flags - only one of TMNOFLAGS, TMJOIN, or TMRESUME. */
if (xid == null ||
(tmJoin && tmResume) ||
(!tmJoin &&
!tmResume &&
flags != XAResource.TMNOFLAGS)) {
throw new XAException(XAException.XAER_INVAL);
}
try {
Transaction txn = getXATransaction(xid);
TxnManager txnMgr = getNonNullEnvImpl().getTxnManager();
if (flags == XAResource.TMNOFLAGS) {
/*
* If neither RESUME nor JOIN was set, make sure xid doesn't
* exist in allXATxns. Throw XAER_DUPID if it does.
*/
if (txn == null) {
if (DEBUG) {
System.out.println
("Transaction for XID " + xid + " being created");
}
txn = beginTransaction(null, null);
setXATransaction(xid, txn);
} else {
throw new XAException(XAException.XAER_DUPID);
}
} else if (tmJoin) {
if (txn == null) {
throw new XAException(XAException.XAER_NOTA);
}
if (txnMgr.getTxnForThread() != null ||
txn.getPrepared()) {
throw new XAException(XAException.XAER_PROTO);
}
} else if (tmResume) {
if (txn == null) {
throw new XAException(XAException.XAER_NOTA);
}
if (!txn.getTxn().isSuspended()) {
throw new XAException(XAException.XAER_PROTO);
}
txn.getTxn().setSuspended(false);
}
if (DEBUG) {
System.out.println
("Setting Transaction for " + Thread.currentThread());
}
txnMgr.setTxnForThread(txn);
} catch (DatabaseException DE) {
if (DEBUG) {
System.out.println("*** start exception");
}
throwNewXAException(DE);
}
if (DEBUG) {
System.out.println("*** start finished");
}
}
private void throwNewXAException(Exception E)
throws XAException {
XAException ret = new XAException(E.toString());
ret.initCause(E);
throw ret;
}
}