org.ehcache.transactions.xa.internal.EhcacheXAResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache-transactions Show documentation
Show all versions of ehcache-transactions Show documentation
The transactions module of Ehcache 3
/*
* Copyright Terracotta, Inc.
*
* 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.
*/
package org.ehcache.transactions.xa.internal;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.core.spi.store.Store;
import org.ehcache.transactions.xa.EhcacheXAException;
import org.ehcache.transactions.xa.internal.journal.Journal;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The Ehcache {@link XAResource} implementation.
*
* @author Ludovic Orban
*/
public class EhcacheXAResource implements XAResource {
private static final int DEFAULT_TRANSACTION_TIMEOUT_SECONDS = 30;
private final Store> underlyingStore;
private final Journal journal;
private final XATransactionContextFactory transactionContextFactory;
private volatile Xid currentXid;
private volatile int transactionTimeoutInSeconds = DEFAULT_TRANSACTION_TIMEOUT_SECONDS;
public EhcacheXAResource(Store> underlyingStore, Journal journal, XATransactionContextFactory transactionContextFactory) {
this.underlyingStore = underlyingStore;
this.journal = journal;
this.transactionContextFactory = transactionContextFactory;
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
if (currentXid != null) {
throw new EhcacheXAException("Cannot commit a non-ended start on : " + xid, XAException.XAER_PROTO);
}
TransactionId transactionId = new TransactionId(xid);
XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
try {
if (onePhase) {
if (transactionContext == null) {
throw new EhcacheXAException("Cannot commit in one phase unknown XID : " + xid, XAException.XAER_NOTA);
}
try {
transactionContext.commitInOnePhase();
} catch (XATransactionContext.TransactionTimeoutException tte) {
throw new EhcacheXAException("Transaction timed out", XAException.XA_RBTIMEOUT);
}
} else {
XATransactionContext commitContext = transactionContext;
if (commitContext == null) {
// recovery commit
commitContext = new XATransactionContext(new TransactionId(new SerializableXid(xid)), underlyingStore, journal, null, 0L);
}
commitContext.commit(transactionContext == null);
}
} catch (IllegalArgumentException iae) {
throw new EhcacheXAException("Cannot commit unknown XID : " + xid, XAException.XAER_NOTA);
} catch (IllegalStateException ise) {
throw new EhcacheXAException("Cannot commit XID : " + xid, XAException.XAER_PROTO, ise);
} catch (StoreAccessException cae) {
throw new EhcacheXAException("Cannot commit XID : " + xid, XAException.XAER_RMERR, cae);
} finally {
if (transactionContext != null) {
transactionContextFactory.destroy(transactionId);
}
}
}
@Override
public void forget(Xid xid) throws XAException {
TransactionId transactionId = new TransactionId(xid);
if (journal.isInDoubt(transactionId)) {
throw new EhcacheXAException("Cannot forget in-doubt XID : " + xid, XAException.XAER_PROTO);
}
if (!journal.isHeuristicallyTerminated(transactionId)) {
throw new EhcacheXAException("Cannot forget unknown XID : " + xid, XAException.XAER_NOTA);
}
journal.forget(transactionId);
}
@Override
public int getTransactionTimeout() throws XAException {
return transactionTimeoutInSeconds;
}
@Override
public boolean isSameRM(XAResource xaResource) throws XAException {
return xaResource == this;
}
@Override
public int prepare(Xid xid) throws XAException {
if (currentXid != null) {
throw new EhcacheXAException("Cannot prepare a non-ended start on : " + xid, XAException.XAER_PROTO);
}
TransactionId transactionId = new TransactionId(xid);
XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
if (transactionContext == null) {
throw new EhcacheXAException("Cannot prepare unknown XID : " + xid, XAException.XAER_NOTA);
}
boolean destroyContext = false;
try {
destroyContext = transactionContext.prepare() == 0;
return destroyContext ? XA_RDONLY : XA_OK;
} catch (XATransactionContext.TransactionTimeoutException tte) {
destroyContext = true;
throw new EhcacheXAException("Transaction timed out", XAException.XA_RBTIMEOUT);
} catch (IllegalStateException ise) {
throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_PROTO, ise);
} catch (StoreAccessException cae) {
throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_RMERR, cae);
} finally {
if (destroyContext) {
transactionContextFactory.destroy(transactionId);
}
}
}
@Override
public Xid[] recover(int flags) throws XAException {
if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMSTARTRSCAN && flags != XAResource.TMENDRSCAN && flags != (XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN)) {
throw new EhcacheXAException("Recover flags not supported : " + xaResourceFlagsToString(flags), XAException.XAER_INVAL);
}
if ((flags & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN) {
List xids = new ArrayList();
Set transactionIds = journal.recover().keySet();
for (TransactionId transactionId : transactionIds) {
// filter-out in-flight tx
if (!transactionContextFactory.contains(transactionId)) {
xids.add(transactionId.getSerializableXid());
}
}
return xids.toArray(new Xid[xids.size()]);
}
return new Xid[0];
}
@Override
public void rollback(Xid xid) throws XAException {
if (currentXid != null) {
throw new EhcacheXAException("Cannot rollback a non-ended start on : " + xid, XAException.XAER_PROTO);
}
TransactionId transactionId = new TransactionId(xid);
XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
try {
XATransactionContext rollbackContext = transactionContext;
if (rollbackContext == null) {
// recovery rollback
rollbackContext = new XATransactionContext(new TransactionId(new SerializableXid(xid)), underlyingStore, journal, null, 0L);
}
rollbackContext.rollback(transactionContext == null);
} catch (IllegalStateException ise) {
throw new EhcacheXAException("Cannot rollback unknown XID : " + xid, XAException.XAER_NOTA);
} catch (StoreAccessException cae) {
throw new EhcacheXAException("Cannot rollback XID : " + xid, XAException.XAER_RMERR, cae);
} finally {
if (transactionContext != null) {
transactionContextFactory.destroy(transactionId);
}
}
}
@Override
public boolean setTransactionTimeout(int seconds) throws XAException {
if (seconds < 0) {
throw new EhcacheXAException("Transaction timeout must be >= 0", XAException.XAER_INVAL);
}
if (seconds == 0) {
this.transactionTimeoutInSeconds = DEFAULT_TRANSACTION_TIMEOUT_SECONDS;
} else {
this.transactionTimeoutInSeconds = seconds;
}
return true;
}
@Override
public void start(Xid xid, int flag) throws XAException {
if (flag != XAResource.TMNOFLAGS && flag != XAResource.TMJOIN) {
throw new EhcacheXAException("Start flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL);
}
if (currentXid != null) {
throw new EhcacheXAException("Already started on : " + xid, XAException.XAER_PROTO);
}
TransactionId transactionId = new TransactionId(xid);
XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
if (flag == XAResource.TMNOFLAGS) {
if (transactionContext == null) {
transactionContext = transactionContextFactory.createTransactionContext(transactionId, underlyingStore, journal, transactionTimeoutInSeconds);
} else {
throw new EhcacheXAException("Cannot start in parallel on two XIDs : starting " + xid, XAException.XAER_RMERR);
}
} else {
if (transactionContext == null) {
throw new EhcacheXAException("Cannot join unknown XID : " + xid, XAException.XAER_NOTA);
}
}
if (transactionContext.hasTimedOut()) {
transactionContextFactory.destroy(transactionId);
throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT);
}
currentXid = xid;
}
@Override
public void end(Xid xid, int flag) throws XAException {
if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL) {
throw new EhcacheXAException("End flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL);
}
if (currentXid == null) {
throw new EhcacheXAException("Not started on : " + xid, XAException.XAER_PROTO);
}
TransactionId transactionId = new TransactionId(currentXid);
XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
if (transactionContext == null) {
throw new EhcacheXAException("Cannot end unknown XID : " + xid, XAException.XAER_NOTA);
}
boolean destroyContext = false;
if (flag == XAResource.TMFAIL) {
destroyContext = true;
}
currentXid = null;
try {
if (transactionContext.hasTimedOut()) {
destroyContext = true;
throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT);
}
} finally {
if (destroyContext) {
transactionContextFactory.destroy(transactionId);
}
}
}
public XATransactionContext getCurrentContext() {
if (currentXid == null) {
return null;
}
return transactionContextFactory.get(new TransactionId(currentXid));
}
private static String xaResourceFlagsToString(int flags) {
StringBuilder sb = new StringBuilder();
Set> entries = XARESOURCE_FLAGS_TO_NAMES.entrySet();
for (Map.Entry entry : entries) {
int constant = entry.getKey();
String name = entry.getValue();
if (constant != 0 && (flags & constant) == constant) {
sb.append(name).append("|");
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
} else {
sb.append(XARESOURCE_FLAGS_TO_NAMES.get(0));
}
return sb.toString();
}
private static final Map XARESOURCE_FLAGS_TO_NAMES = new HashMap();
static {
try {
for (Field field : XAResource.class.getFields()) {
String name = field.getName();
if (field.getType().equals(int.class) && name.startsWith("TM")) {
Integer contant = (Integer) field.get(XAResource.class);
XARESOURCE_FLAGS_TO_NAMES.put(contant, name);
}
}
} catch (IllegalAccessException iae) {
throw new RuntimeException("Cannot initialize XAResource flags map", iae);
}
}
}