/*
* Copyright 2012-2016 the original author or authors.
*
* 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.springframework.boot.jta.narayana;
import java.sql.SQLException;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* XAResourceRecoveryHelper implementation which gets XIDs, which needs to be recovered,
* from the database.
*
* @author Gytis Trikleris
* @since 1.4.0
*/
public class DataSourceXAResourceRecoveryHelper
implements XAResourceRecoveryHelper, XAResource {
private static final XAResource[] NO_XA_RESOURCES = {};
private static final Log logger = LogFactory
.getLog(DataSourceXAResourceRecoveryHelper.class);
private final XADataSource xaDataSource;
private final String user;
private final String password;
private XAConnection xaConnection;
private XAResource delegate;
/**
* Create a new {@link DataSourceXAResourceRecoveryHelper} instance.
* @param xaDataSource the XA data source
*/
public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource) {
this(xaDataSource, null, null);
}
/**
* Create a new {@link DataSourceXAResourceRecoveryHelper} instance.
* @param xaDataSource the XA data source
* @param user the database user or {@code null}
* @param password the database password or {@code null}
*/
public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user,
String password) {
Assert.notNull(xaDataSource, "XADataSource must not be null");
this.xaDataSource = xaDataSource;
this.user = user;
this.password = password;
}
@Override
public boolean initialise(String properties) {
return true;
}
@Override
public XAResource[] getXAResources() {
if (connect()) {
return new XAResource[] { this };
}
return NO_XA_RESOURCES;
}
private boolean connect() {
if (this.delegate == null) {
try {
this.xaConnection = getXaConnection();
this.delegate = this.xaConnection.getXAResource();
}
catch (SQLException ex) {
logger.warn("Failed to create connection", ex);
return false;
}
}
return true;
}
private XAConnection getXaConnection() throws SQLException {
if (this.user == null && this.password == null) {
return this.xaDataSource.getXAConnection();
}
return this.xaDataSource.getXAConnection(this.user, this.password);
}
@Override
public Xid[] recover(int flag) throws XAException {
try {
return getDelegate(true).recover(flag);
}
finally {
if (flag == XAResource.TMENDRSCAN) {
disconnect();
}
}
}
private void disconnect() throws XAException {
try {
this.xaConnection.close();
}
catch (SQLException e) {
logger.warn("Failed to close connection", e);
}
finally {
this.xaConnection = null;
this.delegate = null;
}
}
@Override
public void start(Xid xid, int flags) throws XAException {
getDelegate(true).start(xid, flags);
}
@Override
public void end(Xid xid, int flags) throws XAException {
getDelegate(true).end(xid, flags);
}
@Override
public int prepare(Xid xid) throws XAException {
return getDelegate(true).prepare(xid);
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
getDelegate(true).commit(xid, onePhase);
}
@Override
public void rollback(Xid xid) throws XAException {
getDelegate(true).rollback(xid);
}
@Override
public boolean isSameRM(XAResource xaResource) throws XAException {
return getDelegate(true).isSameRM(xaResource);
}
@Override
public void forget(Xid xid) throws XAException {
getDelegate(true).forget(xid);
}
@Override
public int getTransactionTimeout() throws XAException {
return getDelegate(true).getTransactionTimeout();
}
@Override
public boolean setTransactionTimeout(int seconds) throws XAException {
return getDelegate(true).setTransactionTimeout(seconds);
}
private XAResource getDelegate(boolean required) {
Assert.state(this.delegate != null || !required,
"Connection has not been opened");
return this.delegate;
}
}