/*
* 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.openejb.resource.jdbc.managed.local;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LocalXAResource implements XAResource {
private final Connection connection;
private Xid currentXid;
private boolean originalAutoCommit;
private final Lock lock = new ReentrantLock();
public LocalXAResource(final Connection localTransaction) {
connection = localTransaction;
}
public Xid getXid() {
checkLock();
return currentXid;
}
@Override
public void start(final Xid xid, final int flag) throws XAException {
try {
lock.tryLock(10, TimeUnit.MINUTES);
} catch (final InterruptedException e) {
throw (XAException) new XAException("can't get lock").initCause(cantGetLock());
}
if (flag == XAResource.TMNOFLAGS) {
if (currentXid != null) {
throw new XAException("Already enlisted in another transaction with xid " + xid);
}
// save off the current auto commit flag so it can be restored after the transaction completes
try {
originalAutoCommit = connection.getAutoCommit();
} catch (final SQLException ignored) {
originalAutoCommit = true;
}
try {
connection.setAutoCommit(false);
} catch (final SQLException e) {
throw (XAException) new XAException("Count not turn off auto commit for a XA transaction").initCause(e);
}
this.currentXid = xid;
} else if (flag == XAResource.TMRESUME) {
if (xid != currentXid) {
throw new XAException("Attempting to resume in different transaction: expected " + currentXid + ", but was " + xid);
}
} else {
throw new XAException("Unknown start flag " + flag);
}
}
private RuntimeException cantGetLock() {
return new IllegalStateException("can't get lock on resource with Xid " + currentXid + " from thread " + Thread.currentThread().getName());
}
@Override
public void end(final Xid xid, final int flag) throws XAException {
try {
if (xid == null) {
throw new NullPointerException("xid is null");
}
if (!this.currentXid.equals(xid)) {
throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
}
} finally {
lock.unlock();
}
}
@Override
public int prepare(final Xid xid) {
checkLock();
try {
if (connection.isReadOnly()) {
connection.setAutoCommit(originalAutoCommit);
return XAResource.XA_RDONLY;
}
} catch (final SQLException ignored) {
// no-op
}
return XAResource.XA_OK;
}
@Override
public void commit(final Xid xid, final boolean flag) throws XAException {
checkLock();
if (xid == null) {
throw new NullPointerException("xid is null");
}
if (!currentXid.equals(xid)) {
throw new XAException("Invalid Xid: expected " + currentXid + ", but was " + xid);
}
try {
if (connection.isClosed()) {
throw new XAException("Conection is closed");
}
if (!connection.isReadOnly()) {
connection.commit();
}
} catch (final SQLException e) {
throw (XAException) new XAException().initCause(e);
} finally {
try {
connection.setAutoCommit(originalAutoCommit);
} catch (final SQLException e) {
// no-op
}
currentXid = null;
}
}
@Override
public void rollback(final Xid xid) throws XAException {
checkLock();
if (xid == null) {
throw new NullPointerException("xid is null");
}
if (!currentXid.equals(xid)) {
throw new XAException("Invalid Xid: expected " + currentXid + ", but was " + xid);
}
try {
connection.rollback();
} catch (final SQLException e) {
throw (XAException) new XAException().initCause(e);
} finally {
try {
connection.setAutoCommit(originalAutoCommit);
} catch (final SQLException e) {
// no-op
}
this.currentXid = null;
}
}
@Override
public boolean isSameRM(final XAResource xaResource) {
return this == xaResource;
}
@Override
public void forget(final Xid xid) {
checkLock();
if (xid != null && currentXid.equals(xid)) {
currentXid = null;
}
}
@Override
public Xid[] recover(final int flag) {
return new Xid[0];
}
@Override
public int getTransactionTimeout() {
return 0;
}
@Override
public boolean setTransactionTimeout(final int transactionTimeout) {
return false;
}
private void checkLock() {
if (!lock.tryLock()) {
throw cantGetLock();
}
}
}