chore(project): 添加项目配置文件和忽略规则

- 添加 Babel 配置文件支持 ES6+ 语法转换
- 添加 ESLint 忽略规则和配置文件
- 添加 Git 忽略规则文件
- 添加 Travis CI 配置文件
- 添加 1.4.2 版本变更日志文件
- 添加 Helm 图表辅助模板文件
- 添加 Helm 忽略规则文件
This commit is contained in:
2026-03-27 17:36:48 +08:00
commit c2453d6434
1703 changed files with 277582 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm;
import io.seata.core.exception.TmTransactionException;
import io.seata.core.exception.TransactionException;
import io.seata.core.exception.TransactionExceptionCode;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.core.protocol.ResultCode;
import io.seata.core.protocol.transaction.AbstractTransactionRequest;
import io.seata.core.protocol.transaction.AbstractTransactionResponse;
import io.seata.core.protocol.transaction.GlobalBeginRequest;
import io.seata.core.protocol.transaction.GlobalBeginResponse;
import io.seata.core.protocol.transaction.GlobalCommitRequest;
import io.seata.core.protocol.transaction.GlobalCommitResponse;
import io.seata.core.protocol.transaction.GlobalReportRequest;
import io.seata.core.protocol.transaction.GlobalReportResponse;
import io.seata.core.protocol.transaction.GlobalRollbackRequest;
import io.seata.core.protocol.transaction.GlobalRollbackResponse;
import io.seata.core.protocol.transaction.GlobalStatusRequest;
import io.seata.core.protocol.transaction.GlobalStatusResponse;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
import java.util.concurrent.TimeoutException;
/**
* The type Default transaction manager.
*
* @author sharajava
*/
public class DefaultTransactionManager implements TransactionManager {
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setXid(xid);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
globalRollback.setXid(xid);
GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback);
return response.getGlobalStatus();
}
@Override
public GlobalStatus getStatus(String xid) throws TransactionException {
GlobalStatusRequest queryGlobalStatus = new GlobalStatusRequest();
queryGlobalStatus.setXid(xid);
GlobalStatusResponse response = (GlobalStatusResponse) syncCall(queryGlobalStatus);
return response.getGlobalStatus();
}
@Override
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
GlobalReportRequest globalReport = new GlobalReportRequest();
globalReport.setXid(xid);
globalReport.setGlobalStatus(globalStatus);
GlobalReportResponse response = (GlobalReportResponse) syncCall(globalReport);
return response.getGlobalStatus();
}
private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
try {
return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);
} catch (TimeoutException toe) {
throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
/**
* The type Tm client.
*
* @author slievrly
*/
public class TMClient {
/**
* Init.
*
* @param applicationId the application id
* @param transactionServiceGroup the transaction service group
*/
public static void init(String applicationId, String transactionServiceGroup) {
init(applicationId, transactionServiceGroup, null, null);
}
/**
* Init.
*
* @param applicationId the application id
* @param transactionServiceGroup the transaction service group
* @param accessKey the access key
* @param secretKey the secret key
*/
public static void init(String applicationId, String transactionServiceGroup, String accessKey, String secretKey) {
TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup, accessKey, secretKey);
tmNettyRemotingClient.init();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm;
import io.seata.common.exception.ShouldNeverHappenException;
import io.seata.common.loader.EnhancedServiceLoader;
import io.seata.core.model.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The type Default transaction manager.
*
* @author sharajava
*/
public class TransactionManagerHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionManagerHolder.class);
private static class SingletonHolder {
private static TransactionManager INSTANCE = null;
static {
try {
INSTANCE = EnhancedServiceLoader.load(TransactionManager.class);
LOGGER.info("TransactionManager Singleton {}", INSTANCE);
} catch (Throwable anyEx) {
LOGGER.error("Failed to load TransactionManager Singleton! ", anyEx);
}
}
}
/**
* Get transaction manager.
*
* @return the transaction manager
*/
public static TransactionManager get() {
if (SingletonHolder.INSTANCE == null) {
throw new ShouldNeverHappenException("TransactionManager is NOT ready!");
}
return SingletonHolder.INSTANCE;
}
/**
* Set a TM instance.
*
* @param mock commonly used for test mocking
*/
public static void set(TransactionManager mock) {
SingletonHolder.INSTANCE = mock;
}
private TransactionManagerHolder() {
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import java.util.concurrent.TimeUnit;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import io.seata.common.thread.NamedThreadFactory;
import io.seata.core.exception.TransactionException;
import io.seata.core.logger.StackTraceLogger;
import io.seata.core.model.GlobalStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The type Default failure handler.
*
* @author slievrly
*/
public class DefaultFailureHandlerImpl implements FailureHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFailureHandlerImpl.class);
/**
* Retry 1 hours by default
*/
private static final int RETRY_MAX_TIMES = 6 * 60;
private static final long SCHEDULE_INTERVAL_SECONDS = 10;
private static final long TICK_DURATION = 1;
private static final int TICKS_PER_WHEEL = 8;
private HashedWheelTimer timer = new HashedWheelTimer(
new NamedThreadFactory("failedTransactionRetry", 1),
TICK_DURATION, TimeUnit.SECONDS, TICKS_PER_WHEEL);
@Override
public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
LOGGER.warn("Failed to begin transaction. ", cause);
}
@Override
public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
LOGGER.warn("Failed to commit transaction[" + tx.getXid() + "]", cause);
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Committed), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
@Override
public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
LOGGER.warn("Failed to rollback transaction[" + tx.getXid() + "]", originalException);
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Rollbacked), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
@Override
public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {
StackTraceLogger.warn(LOGGER, originalException, "Retrying to rollback transaction[{}]", new String[] {tx.getXid()});
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.RollbackRetrying), SCHEDULE_INTERVAL_SECONDS,
TimeUnit.SECONDS);
}
protected class CheckTimerTask implements TimerTask {
private final GlobalTransaction tx;
private final GlobalStatus required;
private int count = 0;
private boolean isStopped = false;
protected CheckTimerTask(final GlobalTransaction tx, GlobalStatus required) {
this.tx = tx;
this.required = required;
}
@Override
public void run(Timeout timeout) throws Exception {
if (!isStopped) {
if (++count > RETRY_MAX_TIMES) {
LOGGER.error("transaction [{}] retry fetch status times exceed the limit [{} times]", tx.getXid(), RETRY_MAX_TIMES);
return;
}
isStopped = shouldStop(tx, required);
timer.newTimeout(this, SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}
}
private boolean shouldStop(final GlobalTransaction tx, GlobalStatus required) {
try {
GlobalStatus status = tx.getStatus();
LOGGER.info("transaction [{}] current status is [{}]", tx.getXid(), status);
if (status == required || status == GlobalStatus.Finished) {
return true;
}
} catch (TransactionException e) {
LOGGER.error("fetch GlobalTransaction status error", e);
}
return false;
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.config.ConfigurationFactory;
import io.seata.core.constants.ConfigurationKeys;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.seata.common.DefaultValues.DEFAULT_TM_COMMIT_RETRY_COUNT;
import static io.seata.common.DefaultValues.DEFAULT_TM_ROLLBACK_RETRY_COUNT;
/**
* The type Default global transaction.
*
* @author sharajava
*/
public class DefaultGlobalTransaction implements GlobalTransaction {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGlobalTransaction.class);
private static final int DEFAULT_GLOBAL_TX_TIMEOUT = 60000;
private static final String DEFAULT_GLOBAL_TX_NAME = "default";
private TransactionManager transactionManager;
private String xid;
private GlobalStatus status;
private GlobalTransactionRole role;
private static final int COMMIT_RETRY_COUNT = ConfigurationFactory.getInstance().getInt(
ConfigurationKeys.CLIENT_TM_COMMIT_RETRY_COUNT, DEFAULT_TM_COMMIT_RETRY_COUNT);
private static final int ROLLBACK_RETRY_COUNT = ConfigurationFactory.getInstance().getInt(
ConfigurationKeys.CLIENT_TM_ROLLBACK_RETRY_COUNT, DEFAULT_TM_ROLLBACK_RETRY_COUNT);
/**
* Instantiates a new Default global transaction.
*/
DefaultGlobalTransaction() {
this(null, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher);
}
/**
* Instantiates a new Default global transaction.
*
* @param xid the xid
* @param status the status
* @param role the role
*/
DefaultGlobalTransaction(String xid, GlobalStatus status, GlobalTransactionRole role) {
this.transactionManager = TransactionManagerHolder.get();
this.xid = xid;
this.status = status;
this.role = role;
}
@Override
public void begin() throws TransactionException {
begin(DEFAULT_GLOBAL_TX_TIMEOUT);
}
@Override
public void begin(int timeout) throws TransactionException {
begin(timeout, DEFAULT_GLOBAL_TX_NAME);
}
@Override
public void begin(int timeout, String name) throws TransactionException {
if (role != GlobalTransactionRole.Launcher) {
assertXIDNotNull();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);
}
return;
}
assertXIDNull();
String currentXid = RootContext.getXID();
if (currentXid != null) {
throw new IllegalStateException("Global transaction already exists," +
" can't begin a new global transaction, currentXid = " + currentXid);
}
xid = transactionManager.begin(null, null, name, timeout);
status = GlobalStatus.Begin;
RootContext.bind(xid);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Begin new global transaction [{}]", xid);
}
}
@Override
public void commit() throws TransactionException {
if (role == GlobalTransactionRole.Participant) {
// Participant has no responsibility of committing
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignore Commit(): just involved in global transaction [{}]", xid);
}
return;
}
assertXIDNotNull();
int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT;
try {
while (retry > 0) {
try {
status = transactionManager.commit(xid);
break;
} catch (Throwable ex) {
LOGGER.error("Failed to report global commit [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
retry--;
if (retry == 0) {
throw new TransactionException("Failed to report global commit", ex);
}
}
}
} finally {
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("[{}] commit status: {}", xid, status);
}
}
@Override
public void rollback() throws TransactionException {
if (role == GlobalTransactionRole.Participant) {
// Participant has no responsibility of rollback
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignore Rollback(): just involved in global transaction [{}]", xid);
}
return;
}
assertXIDNotNull();
int retry = ROLLBACK_RETRY_COUNT <= 0 ? DEFAULT_TM_ROLLBACK_RETRY_COUNT : ROLLBACK_RETRY_COUNT;
try {
while (retry > 0) {
try {
status = transactionManager.rollback(xid);
break;
} catch (Throwable ex) {
LOGGER.error("Failed to report global rollback [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
retry--;
if (retry == 0) {
throw new TransactionException("Failed to report global rollback", ex);
}
}
}
} finally {
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("[{}] rollback status: {}", xid, status);
}
}
@Override
public SuspendedResourcesHolder suspend() throws TransactionException {
// In order to associate the following logs with XID, first get and then unbind.
String xid = RootContext.getXID();
if (xid != null) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Suspending current transaction, xid = {}", xid);
}
RootContext.unbind();
return new SuspendedResourcesHolder(xid);
} else {
return null;
}
}
@Override
public void resume(SuspendedResourcesHolder suspendedResourcesHolder) throws TransactionException {
if (suspendedResourcesHolder == null) {
return;
}
String xid = suspendedResourcesHolder.getXid();
RootContext.bind(xid);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Resumimg the transaction,xid = {}", xid);
}
}
@Override
public GlobalStatus getStatus() throws TransactionException {
if (xid == null) {
return GlobalStatus.UnKnown;
}
status = transactionManager.getStatus(xid);
return status;
}
@Override
public String getXid() {
return xid;
}
@Override
public void globalReport(GlobalStatus globalStatus) throws TransactionException {
assertXIDNotNull();
if (globalStatus == null) {
throw new IllegalStateException();
}
status = transactionManager.globalReport(xid, globalStatus);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("[{}] report status: {}", xid, status);
}
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
@Override
public GlobalStatus getLocalStatus() {
return status;
}
@Override
public GlobalTransactionRole getGlobalTransactionRole() {
return role;
}
private void assertXIDNotNull() {
if (xid == null) {
throw new IllegalStateException();
}
}
private void assertXIDNull() {
if (xid != null) {
throw new IllegalStateException();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
/**
* Callback on failure.
*
* @author slievrly
*/
public interface FailureHandler {
/**
* On begin failure.
*
* @param tx the tx
* @param cause the cause
*/
void onBeginFailure(GlobalTransaction tx, Throwable cause);
/**
* On commit failure.
*
* @param tx the tx
* @param cause the cause
*/
void onCommitFailure(GlobalTransaction tx, Throwable cause);
/**
* On rollback failure.
*
* @param tx the tx
* @param originalException the originalException
*/
void onRollbackFailure(GlobalTransaction tx, Throwable originalException);
/**
* On rollback retrying
*
* @param tx the tx
* @param originalException the originalException
*/
void onRollbackRetrying(GlobalTransaction tx, Throwable originalException);
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
/**
* Global transaction.
*
* @author sharajava
*/
public interface GlobalTransaction {
/**
* Begin a new global transaction with default timeout and name.
*
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void begin() throws TransactionException;
/**
* Begin a new global transaction with given timeout and default name.
*
* @param timeout Global transaction timeout in MILLISECONDS
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void begin(int timeout) throws TransactionException;
/**
* Begin a new global transaction with given timeout and given name.
*
* @param timeout Given timeout in MILLISECONDS.
* @param name Given name.
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void begin(int timeout, String name) throws TransactionException;
/**
* Commit the global transaction.
*
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void commit() throws TransactionException;
/**
* Rollback the global transaction.
*
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void rollback() throws TransactionException;
/**
* Suspend the global transaction.
*
* @return the SuspendedResourcesHolder which holds the suspend resources
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* @see SuspendedResourcesHolder
*/
SuspendedResourcesHolder suspend() throws TransactionException;
/**
* Resume the global transaction.
*
* @param suspendedResourcesHolder the suspended resources to resume
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
* @see SuspendedResourcesHolder
*/
void resume(SuspendedResourcesHolder suspendedResourcesHolder) throws TransactionException;
/**
* Ask TC for current status of the corresponding global transaction.
*
* @return Status of the corresponding global transaction.
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
* @see GlobalStatus
*/
GlobalStatus getStatus() throws TransactionException;
/**
* Get XID.
*
* @return XID. xid
*/
String getXid();
/**
* report the global transaction status.
*
* @param globalStatus global status.
*
* @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
* out.
*/
void globalReport(GlobalStatus globalStatus) throws TransactionException;
/**
* local status of the global transaction.
*
* @return Status of the corresponding global transaction.
* @see GlobalStatus
*/
GlobalStatus getLocalStatus();
/**
* get global transaction role.
*
* @return global transaction Role.
* @see GlobalTransactionRole
*/
GlobalTransactionRole getGlobalTransactionRole();
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
/**
* GlobalTransaction API
*
* @author sharajava
*/
public class GlobalTransactionContext {
private GlobalTransactionContext() {
}
/**
* Try to create a new GlobalTransaction.
*
* @return the new global transaction
*/
public static GlobalTransaction createNew() {
return new DefaultGlobalTransaction();
}
/**
* Get GlobalTransaction instance bind on current thread.
*
* @return null if no transaction context there.
*/
public static GlobalTransaction getCurrent() {
String xid = RootContext.getXID();
if (xid == null) {
return null;
}
return new DefaultGlobalTransaction(xid, GlobalStatus.Begin, GlobalTransactionRole.Participant);
}
/**
* Get GlobalTransaction instance bind on current thread. Create a new on if no existing there.
*
* @return new context if no existing there.
*/
public static GlobalTransaction getCurrentOrCreate() {
GlobalTransaction tx = getCurrent();
if (tx == null) {
return createNew();
}
return tx;
}
/**
* Reload GlobalTransaction instance according to the given XID
*
* @param xid the xid
* @return reloaded transaction instance.
* @throws TransactionException the transaction exception
*/
public static GlobalTransaction reload(String xid) throws TransactionException {
return new DefaultGlobalTransaction(xid, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher) {
@Override
public void begin(int timeout, String name) throws TransactionException {
throw new IllegalStateException("Never BEGIN on a RELOADED GlobalTransaction. ");
}
};
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
/**
* Role of current thread involve in a global transaction.
*
* @author sharajava
*/
public enum GlobalTransactionRole {
/**
* The Launcher.
*/
// The one begins the current global transaction.
Launcher,
/**
* The Participant.
*/
// The one just joins into a existing global transaction.
Participant
}

View File

@@ -0,0 +1,201 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.tm.api.transaction.TransactionInfo;
/**
* Callback for executing business logic in a global transaction.
*
* @author sharajava
*/
public interface TransactionalExecutor {
/**
* Execute the business logic here.
*
* @return What the business logic returns.
* @throws Throwable Any throwable during executing.
*/
Object execute() throws Throwable;
/**
* transaction conf or other attr
* @return transaction info
*/
TransactionInfo getTransactionInfo();
/**
* The enum Code.
*/
enum Code {
/**
* Begin failure code.
*/
//
BeginFailure,
/**
* Commit failure code.
*/
//
CommitFailure,
/**
* Rollback failure code.
*/
//
RollbackFailure,
/**
* Rollback done code.
*/
//
RollbackDone,
/**
* Report failure code.
*/
//
ReportFailure,
/**
* Rollback retrying code.
*/
//
RollbackRetrying
}
/**
* The type Execution exception.
*/
class ExecutionException extends Exception {
private GlobalTransaction transaction;
private Code code;
private Throwable originalException;
/**
* Instantiates a new Execution exception.
*
* @param transaction the transaction
* @param cause the cause
* @param code the code
*/
public ExecutionException(GlobalTransaction transaction, Throwable cause, Code code) {
this(transaction, cause, code, null);
}
/**
* Instantiates a new Execution exception.
*
* @param transaction the transaction
* @param code the code
* @param originalException the original exception
*/
public ExecutionException(GlobalTransaction transaction, Code code, Throwable originalException) {
this(transaction, null, code, originalException);
}
/**
* Instantiates a new Execution exception.
*
* @param transaction the transaction
* @param cause the cause
* @param code the code
* @param originalException the original exception
*/
public ExecutionException(GlobalTransaction transaction, Throwable cause, Code code,
Throwable originalException) {
this(transaction, null, cause, code, originalException);
}
/**
* Instantiates a new Execution exception.
*
* @param transaction the transaction
* @param message the message
* @param cause the cause
* @param code the code
* @param originalException the original exception
*/
public ExecutionException(GlobalTransaction transaction, String message, Throwable cause, Code code,
Throwable originalException) {
super(message, cause);
this.transaction = transaction;
this.code = code;
this.originalException = originalException;
}
/**
* Gets transaction.
*
* @return the transaction
*/
public GlobalTransaction getTransaction() {
return transaction;
}
/**
* Sets transaction.
*
* @param transaction the transaction
*/
public void setTransaction(GlobalTransaction transaction) {
this.transaction = transaction;
}
/**
* Gets code.
*
* @return the code
*/
public Code getCode() {
return code;
}
/**
* Sets code.
*
* @param code the code
*/
public void setCode(Code code) {
this.code = code;
}
/**
* Gets original exception.
*
* @return the original exception
*/
public Throwable getOriginalException() {
return originalException;
}
/**
* Sets original exception.
*
* @param originalException the original exception
*/
public void setOriginalException(Throwable originalException) {
this.originalException = originalException;
}
}
}

View File

@@ -0,0 +1,301 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import java.util.List;
import io.seata.common.exception.ShouldNeverHappenException;
import io.seata.core.context.GlobalLockConfigHolder;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalLockConfig;
import io.seata.core.model.GlobalStatus;
import io.seata.tm.api.transaction.Propagation;
import io.seata.tm.api.transaction.SuspendedResourcesHolder;
import io.seata.tm.api.transaction.TransactionHook;
import io.seata.tm.api.transaction.TransactionHookManager;
import io.seata.tm.api.transaction.TransactionInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Template of executing business logic with a global transaction.
*
* @author sharajava
*/
public class TransactionalTemplate {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionalTemplate.class);
/**
* Execute object.
*
* @param business the business
* @return the object
* @throws TransactionalExecutor.ExecutionException the execution exception
*/
public Object execute(TransactionalExecutor business) throws Throwable {
// 1. Get transactionInfo
TransactionInfo txInfo = business.getTransactionInfo();
if (txInfo == null) {
throw new ShouldNeverHappenException("transactionInfo does not exist");
}
// 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 Handle the transaction propagation.
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
case NOT_SUPPORTED:
// If transaction is existing, suspend it.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
}
// Execute without transaction and return.
return business.execute();
case REQUIRES_NEW:
// If transaction is existing, suspend it, and then begin new transaction.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
tx = GlobalTransactionContext.createNew();
}
// Continue and execute with new transaction
break;
case SUPPORTS:
// If transaction is not existing, execute without transaction.
if (notExistingTransaction(tx)) {
return business.execute();
}
// Continue and execute with new transaction
break;
case REQUIRED:
// If current transaction is existing, execute with current transaction,
// else continue and execute with new transaction.
break;
case NEVER:
// If transaction is existing, throw exception.
if (existingTransaction(tx)) {
throw new TransactionException(
String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
, tx.getXid()));
} else {
// Execute without transaction and return.
return business.execute();
}
case MANDATORY:
// If transaction is not existing, throw exception.
if (notExistingTransaction(tx)) {
throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
}
// Continue and execute with current transaction.
break;
default:
throw new TransactionException("Not Supported Propagation:" + propagation);
}
// 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
if (tx == null) {
tx = GlobalTransactionContext.createNew();
}
// set current tx config to holder
GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);
try {
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
// else do nothing. Of course, the hooks will still be triggered.
beginTransaction(txInfo, tx);
Object rs;
try {
// Do Your Business
rs = business.execute();
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. everything is fine, commit.
commitTransaction(tx);
return rs;
} finally {
//5. clear
resumeGlobalLockConfig(previousConfig);
triggerAfterCompletion();
cleanUp();
}
} finally {
// If the transaction is suspended, resume it.
if (suspendedResourcesHolder != null) {
tx.resume(suspendedResourcesHolder);
}
}
}
private boolean existingTransaction(GlobalTransaction tx) {
return tx != null;
}
private boolean notExistingTransaction(GlobalTransaction tx) {
return tx == null;
}
private GlobalLockConfig replaceGlobalLockConfig(TransactionInfo info) {
GlobalLockConfig myConfig = new GlobalLockConfig();
myConfig.setLockRetryInternal(info.getLockRetryInternal());
myConfig.setLockRetryTimes(info.getLockRetryTimes());
return GlobalLockConfigHolder.setAndReturnPrevious(myConfig);
}
private void resumeGlobalLockConfig(GlobalLockConfig config) {
if (config != null) {
GlobalLockConfigHolder.setAndReturnPrevious(config);
} else {
GlobalLockConfigHolder.remove();
}
}
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
//roll back
if (txInfo != null && txInfo.rollbackOn(originalException)) {
try {
rollbackTransaction(tx, originalException);
} catch (TransactionException txe) {
// Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, originalException);
}
} else {
// not roll back on this exception, so commit
commitTransaction(tx);
}
}
private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeCommit();
tx.commit();
triggerAfterCommit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
}
private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException {
triggerBeforeRollback();
tx.rollback();
triggerAfterRollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus())
? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException);
}
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeBegin();
tx.begin(txInfo.getTimeOut(), txInfo.getName());
triggerAfterBegin();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
}
private void triggerBeforeBegin() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.beforeBegin();
} catch (Exception e) {
LOGGER.error("Failed execute beforeBegin in hook {}", e.getMessage(), e);
}
}
}
private void triggerAfterBegin() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.afterBegin();
} catch (Exception e) {
LOGGER.error("Failed execute afterBegin in hook {}", e.getMessage(), e);
}
}
}
private void triggerBeforeRollback() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.beforeRollback();
} catch (Exception e) {
LOGGER.error("Failed execute beforeRollback in hook {}", e.getMessage(), e);
}
}
}
private void triggerAfterRollback() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.afterRollback();
} catch (Exception e) {
LOGGER.error("Failed execute afterRollback in hook {}", e.getMessage(), e);
}
}
}
private void triggerBeforeCommit() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.beforeCommit();
} catch (Exception e) {
LOGGER.error("Failed execute beforeCommit in hook {}", e.getMessage(), e);
}
}
}
private void triggerAfterCommit() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.afterCommit();
} catch (Exception e) {
LOGGER.error("Failed execute afterCommit in hook {}", e.getMessage(), e);
}
}
}
private void triggerAfterCompletion() {
for (TransactionHook hook : getCurrentHooks()) {
try {
hook.afterCompletion();
} catch (Exception e) {
LOGGER.error("Failed execute afterCompletion in hook {}", e.getMessage(), e);
}
}
}
private void cleanUp() {
TransactionHookManager.clear();
}
private List<TransactionHook> getCurrentHooks() {
return TransactionHookManager.getHooks();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
/**
* @author guoyao
*/
public class NoRollbackRule extends RollbackRule {
public static final NoRollbackRule DEFAULT_NO_ROLLBACK_RULE = new NoRollbackRule(Throwable.class);
public NoRollbackRule(Class<?> clazz) {
super(clazz);
}
public NoRollbackRule(String exceptionName) {
super(exceptionName);
}
@Override
public boolean equals(Object other) {
return super.equals(other);
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return "No" + super.toString();
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import io.seata.tm.api.TransactionalExecutor;
/**
* Propagation level of global transactions.
*
* @author haozhibei
* @see io.seata.spring.annotation.GlobalTransactional#propagation() // TM annotation
* @see io.seata.spring.annotation.GlobalTransactionalInterceptor#invoke(MethodInvocation) // the interceptor of TM
* @see io.seata.tm.api.TransactionalTemplate#execute(TransactionalExecutor) // the transaction template of TM
*/
public enum Propagation {
/**
* The REQUIRED.
* The default propagation.
*
* <p>
* If transaction is existing, execute with current transaction,
* else execute with new transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* if (tx == null) {
* try {
* tx = beginNewTransaction(); // begin new transaction, is not existing
* Object rs = business.execute(); // execute with new transaction
* commitTransaction(tx);
* return rs;
* } catch (Exception ex) {
* rollbackTransaction(tx);
* throw ex;
* }
* } else {
* return business.execute(); // execute with current transaction
* }
* </pre></code>
* </p>
*/
REQUIRED,
/**
* The REQUIRES_NEW.
*
* <p>
* If transaction is existing, suspend it, and then execute business with new transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* try {
* if (tx != null) {
* suspendedResource = suspendTransaction(tx); // suspend current transaction
* }
* try {
* tx = beginNewTransaction(); // begin new transaction
* Object rs = business.execute(); // execute with new transaction
* commitTransaction(tx);
* return rs;
* } catch (Exception ex) {
* rollbackTransaction(tx);
* throw ex;
* }
* } finally {
* if (suspendedResource != null) {
* resumeTransaction(suspendedResource); // resume transaction
* }
* }
* </pre></code>
* </p>
*/
REQUIRES_NEW,
/**
* The NOT_SUPPORTED.
*
* <p>
* If transaction is existing, suspend it, and then execute business without transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* try {
* if (tx != null) {
* suspendedResource = suspendTransaction(tx); // suspend current transaction
* }
* return business.execute(); // execute without transaction
* } finally {
* if (suspendedResource != null) {
* resumeTransaction(suspendedResource); // resume transaction
* }
* }
* </pre></code>
* </p>
*/
NOT_SUPPORTED,
/**
* The SUPPORTS.
*
* <p>
* If transaction is not existing, execute without global transaction,
* else execute business with current transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* if (tx != null) {
* return business.execute(); // execute with current transaction
* } else {
* return business.execute(); // execute without transaction
* }
* </pre></code>
* </p>
*/
SUPPORTS,
/**
* The NEVER.
*
* <p>
* If transaction is existing, throw exception,
* else execute business without transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* if (tx != null) {
* throw new TransactionException("existing transaction");
* }
* return business.execute(); // execute without transaction
* </pre></code>
* </p>
*/
NEVER,
/**
* The MANDATORY.
*
* <p>
* If transaction is not existing, throw exception,
* else execute business with current transaction.
* </p>
*
* <p>
* The logic is similar to the following code:
* <code><pre>
* if (tx == null) {
* throw new TransactionException("not existing transaction");
* }
* return business.execute(); // execute with current transaction
* </pre></code>
* </p>
*/
MANDATORY
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import io.seata.common.util.StringUtils;
import java.io.Serializable;
/**
* @author guoyao
*/
public class RollbackRule implements Serializable {
private final String exceptionName;
public RollbackRule(String exceptionName) {
if (StringUtils.isNullOrEmpty(exceptionName)) {
throw new IllegalArgumentException("'exceptionName' cannot be null or empty");
}
this.exceptionName = exceptionName;
}
public RollbackRule(Class<?> clazz) {
if (clazz == null) {
throw new NullPointerException("'clazz' cannot be null");
}
if (!Throwable.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException(
"Cannot construct rollback rule from [" + clazz.getName() + "]: it's not a Throwable");
}
this.exceptionName = clazz.getName();
}
public String getExceptionName() {
return this.exceptionName;
}
public int getDepth(Throwable ex) {
return getDepth(ex.getClass(), 0);
}
private int getDepth(Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(this.exceptionName)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass == Throwable.class) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof RollbackRule)) {
return false;
}
RollbackRule rhs = (RollbackRule) other;
return this.exceptionName.equals(rhs.exceptionName);
}
@Override
public int hashCode() {
return this.exceptionName.hashCode();
}
@Override
public String toString() {
return "RollbackRule with pattern [" + this.exceptionName + "]";
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import javax.annotation.Nonnull;
/**
* Holder for suspended resources to support propagation or nested logic.
* Used by {@code suspend} and {@code resume}
*
* @author wangzhongxiang
* @author wang.liang
*/
public class SuspendedResourcesHolder {
/**
* The xid
*/
private String xid;
public SuspendedResourcesHolder(String xid) {
if (xid == null) {
throw new IllegalArgumentException("xid must be not null");
}
this.xid = xid;
}
@Nonnull
public String getXid() {
return xid;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
/**
* @author guoyao
*/
public interface TransactionHook {
/**
* before tx begin
*/
void beforeBegin();
/**
* after tx begin
*/
void afterBegin();
/**
* before tx commit
*/
void beforeCommit();
/**
* after tx commit
*/
void afterCommit();
/**
* before tx rollback
*/
void beforeRollback();
/**
* after tx rollback
*/
void afterRollback();
/**
* after tx all Completed
*/
void afterCompletion();
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
/**
* @author guoyao
*/
public class TransactionHookAdapter implements TransactionHook {
@Override
public void beforeBegin() {
}
@Override
public void afterBegin() {
}
@Override
public void beforeCommit() {
}
@Override
public void afterCommit() {
}
@Override
public void beforeRollback() {
}
@Override
public void afterRollback() {
}
@Override
public void afterCompletion() {
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author guoyao
*/
public final class TransactionHookManager {
private TransactionHookManager() {
}
private static final ThreadLocal<List<TransactionHook>> LOCAL_HOOKS = new ThreadLocal<>();
/**
* get the current hooks
*
* @return TransactionHook list
* @throws IllegalStateException IllegalStateException
*/
public static List<TransactionHook> getHooks() throws IllegalStateException {
List<TransactionHook> hooks = LOCAL_HOOKS.get();
if (hooks == null || hooks.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableList(hooks);
}
/**
* add new hook
*
* @param transactionHook transactionHook
*/
public static void registerHook(TransactionHook transactionHook) {
if (transactionHook == null) {
throw new NullPointerException("transactionHook must not be null");
}
List<TransactionHook> transactionHooks = LOCAL_HOOKS.get();
if (transactionHooks == null) {
LOCAL_HOOKS.set(new ArrayList<>());
}
LOCAL_HOOKS.get().add(transactionHook);
}
/**
* clear hooks
*/
public static void clear() {
LOCAL_HOOKS.remove();
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import io.seata.common.util.CollectionUtils;
import java.io.Serializable;
import java.util.Set;
/**
* @author guoyao
*/
public final class TransactionInfo implements Serializable {
private int timeOut;
private String name;
private Set<RollbackRule> rollbackRules;
private Propagation propagation;
private int lockRetryInternal;
private int lockRetryTimes;
public int getTimeOut() {
return timeOut;
}
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<RollbackRule> getRollbackRules() {
return rollbackRules;
}
public void setRollbackRules(Set<RollbackRule> rollbackRules) {
this.rollbackRules = rollbackRules;
}
public boolean rollbackOn(Throwable ex) {
RollbackRule winner = null;
int deepest = Integer.MAX_VALUE;
if (CollectionUtils.isNotEmpty(rollbackRules)) {
winner = NoRollbackRule.DEFAULT_NO_ROLLBACK_RULE;
for (RollbackRule rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
return !(winner instanceof NoRollbackRule);
}
public Propagation getPropagation() {
if (this.propagation != null) {
return this.propagation;
}
//default propagation
return Propagation.REQUIRED;
}
public void setPropagation(Propagation propagation) {
this.propagation = propagation;
}
public int getLockRetryInternal() {
return lockRetryInternal;
}
public void setLockRetryInternal(int lockRetryInternal) {
this.lockRetryInternal = lockRetryInternal;
}
public int getLockRetryTimes() {
return lockRetryTimes;
}
public void setLockRetryTimes(int lockRetryTimes) {
this.lockRetryTimes = lockRetryTimes;
}
}

View File

@@ -0,0 +1 @@
io.seata.tm.DefaultTransactionManager

View File

@@ -0,0 +1,37 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* test for tmClient
* @author Ifdevil
*/
public class TMClientTest {
private static final String APPLICATION_ID = "my_app_test";
private static final String SERVICE_GROUP = "my_test_tx_group";
@Test
public void testInit(){
TMClient.init(APPLICATION_ID,SERVICE_GROUP);
TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance();
Assertions.assertEquals(tmNettyRemotingClient.getTransactionServiceGroup(),SERVICE_GROUP);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm;
import io.seata.common.exception.ShouldNeverHappenException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author wangwei
*/
class TransactionManagerHolderTest {
@Test
void getTest() {
Assertions.assertThrows(ShouldNeverHappenException.class, () -> { TransactionManagerHolder.set(null);
TransactionManagerHolder.get();});
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import io.seata.tm.api.transaction.TransactionInfo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
* The type Api test.
*/
public class APITest {
private static final String DEFAULT_XID = "1234567890";
private static final String TX_NAME = "test";
private static final int TIME_OUT = 30000;
/**
* Init.
*/
@BeforeAll
public static void init() {
TransactionManagerHolder.set(new TransactionManager() {
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
return DEFAULT_XID;
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
return GlobalStatus.Committed;
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
return GlobalStatus.Rollbacked;
}
@Override
public GlobalStatus getStatus(String xid) throws TransactionException {
return GlobalStatus.Begin;
}
@Override
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
return globalStatus;
}
});
}
/**
* Clean root context.
*/
@AfterEach
public void cleanRootContext() {
RootContext.unbind();
}
/**
* Test current.
*
* @throws Exception the exception
*/
@Test
public void testCurrent() throws Exception {
RootContext.bind(DEFAULT_XID);
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
Assertions.assertEquals(tx.getXid(), DEFAULT_XID);
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
}
/**
* Test new tx.
*
* @throws Exception the exception
*/
@Test
public void testNewTx() throws Exception {
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
Assertions.assertEquals(tx.getStatus(), GlobalStatus.UnKnown);
Assertions.assertNull(tx.getXid());
}
/**
* Test begin.
*
* @throws Exception the exception
*/
@Test
public void testBegin() throws Exception {
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin();
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
Assertions.assertNotNull(tx.getXid());
}
/**
* Test nested commit.
*
* @throws Exception the exception
*/
@Test
public void testNestedCommit() throws Throwable {
TransactionalTemplate template = new TransactionalTemplate();
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
TransactionalTemplate template = new TransactionalTemplate();
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
TransactionalTemplate template = new TransactionalTemplate();
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return null;
}
});
return null;
}
});
return null;
}
});
}
/**
* Test nested rollback.
*
* @throws Exception the exception
*/
@Test
public void testNestedRollback() throws Throwable {
final String oexMsg = "xxx";
TransactionalTemplate template = new TransactionalTemplate();
try {
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
TransactionalTemplate template = new TransactionalTemplate();
try {
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
TransactionalTemplate template = new TransactionalTemplate();
try {
template.execute(new AbstractTransactionalExecutor() {
@Override
public Object execute() throws Throwable {
throw new RuntimeException(oexMsg);
}
});
} catch (TransactionalExecutor.ExecutionException ex) {
throw ex.getOriginalException();
}
return null;
}
});
} catch (TransactionalExecutor.ExecutionException ex) {
throw ex.getOriginalException();
}
return null;
}
});
} catch (TransactionalExecutor.ExecutionException ex) {
Throwable oex = ex.getOriginalException();
Assertions.assertEquals(oex.getMessage(), oexMsg);
}
}
@Test
public void testGlobalReport() throws Exception {
RootContext.bind(DEFAULT_XID);
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.globalReport(tx.getStatus());
Assertions.assertThrows(IllegalStateException.class, () -> tx.globalReport(null));
Assertions.assertThrows(IllegalStateException.class, () -> {
RootContext.unbind();
GlobalTransaction tx2 = GlobalTransactionContext.getCurrentOrCreate();
tx2.globalReport(tx2.getStatus());
});
Assertions.assertEquals(tx.getStatus(), GlobalStatus.Begin);
Assertions.assertNotNull(tx.getXid());
}
private static abstract class AbstractTransactionalExecutor implements TransactionalExecutor {
@Override
public TransactionInfo getTransactionInfo() {
TransactionInfo txInfo = new TransactionInfo();
txInfo.setTimeOut(TIME_OUT);
txInfo.setName(TX_NAME);
return txInfo;
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.netty.util.HashedWheelTimer;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import io.seata.tm.api.transaction.MyRuntimeException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
/**
* @author wangwei
*/
class DefaultFailureHandlerImplTest {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFailureHandlerImplTest.class);
private static final String DEFAULT_XID = "1234567890";
private static GlobalStatus globalStatus = GlobalStatus.Begin;
@BeforeAll
public static void init() {
TransactionManagerHolder.set(new TransactionManager() {
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
return DEFAULT_XID;
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
return GlobalStatus.Committed;
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
return GlobalStatus.Rollbacked;
}
@Override
public GlobalStatus getStatus(String xid) throws TransactionException {
return globalStatus;
}
@Override
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
return globalStatus;
}
});
}
@Test
void onBeginFailure() {
RootContext.bind(DEFAULT_XID);
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
failureHandler.onBeginFailure(tx, new MyRuntimeException("").getCause());
}
@Test
void onCommitFailure() throws Exception{
RootContext.bind(DEFAULT_XID);
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
failureHandler.onCommitFailure(tx, new MyRuntimeException("").getCause());
// get timer
Class<?> c = Class.forName("io.seata.tm.api.DefaultFailureHandlerImpl");
Field field = c.getDeclaredField("timer");
field.setAccessible(true);
HashedWheelTimer timer = (HashedWheelTimer) field.get(failureHandler);
// assert timer pendingCount: first time is 1
Long pendingTimeout = timer.pendingTimeouts();
Assertions.assertEquals(pendingTimeout,1L);
//set globalStatus
globalStatus= GlobalStatus.Committed;
Thread.sleep(25*1000L);
pendingTimeout = timer.pendingTimeouts();
LOGGER.info("pendingTimeout {}" ,pendingTimeout);
//all timer is done
Assertions.assertEquals(pendingTimeout,0L);
}
@Test
void onRollbackFailure() throws Exception {
RootContext.bind(DEFAULT_XID);
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
FailureHandler failureHandler = new DefaultFailureHandlerImpl();
failureHandler.onRollbackFailure(tx, new MyRuntimeException("").getCause());
// get timer
Class<?> c = Class.forName("io.seata.tm.api.DefaultFailureHandlerImpl");
Field field = c.getDeclaredField("timer");
field.setAccessible(true);
HashedWheelTimer timer = (HashedWheelTimer) field.get(failureHandler);
// assert timer pendingCount: first time is 1
Long pendingTimeout = timer.pendingTimeouts();
Assertions.assertEquals(pendingTimeout,1L);
//set globalStatus
globalStatus= GlobalStatus.Rollbacked;
Thread.sleep(25*1000L);
pendingTimeout = timer.pendingTimeouts();
LOGGER.info("pendingTimeout {}" ,pendingTimeout);
//all timer is done
Assertions.assertEquals(pendingTimeout,0L);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import io.seata.tm.api.transaction.MyRuntimeException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
* @author wangwei
*/
class DefaultGlobalTransactionTest {
private static final String DEFAULT_XID = "1234567890";
@BeforeAll
public static void init() {
TransactionManagerHolder.set(new TransactionManager() {
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
return DEFAULT_XID;
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
throw new MyRuntimeException("");
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
throw new MyRuntimeException("");
}
@Override
public GlobalStatus getStatus(String xid) throws TransactionException {
throw new MyRuntimeException("");
}
@Override
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
return globalStatus;
}
});
}
@Test
public void commitRetryExceptionTest() throws TransactionException {
RootContext.unbind();
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin();
Assertions.assertThrows(TransactionException.class, tx::commit);
}
@Test
public void commitNoXIDExceptionTest() throws TransactionException {
RootContext.unbind();
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
Assertions.assertThrows(IllegalStateException.class, tx::commit);
}
@Test
public void rollBackRetryExceptionTest() throws TransactionException {
RootContext.unbind();
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin();
Assertions.assertThrows(TransactionException.class, tx::rollback);
}
@Test
public void rollBackNoXIDExceptionTest() throws TransactionException {
RootContext.unbind();
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx.begin();
Assertions.assertThrows(TransactionException.class, tx::rollback);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import io.seata.core.exception.TransactionException;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
* @author wangwei
*/
class GlobalTransactionContextTest {
private static final String DEFAULT_XID = "1234567890";
@BeforeAll
public static void init() {
TransactionManagerHolder.set(new TransactionManager() {
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
return DEFAULT_XID;
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
return GlobalStatus.Committed;
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
return GlobalStatus.Rollbacked;
}
@Override
public GlobalStatus getStatus(String xid) throws TransactionException {
return GlobalStatus.Begin;
}
@Override
public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException {
return globalStatus;
}
});
}
@Test
void reloadTest() throws TransactionException {
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
tx = GlobalTransactionContext.reload(DEFAULT_XID);
GlobalTransaction finalTx = tx;
Assertions.assertThrows(IllegalStateException.class, finalTx::begin);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api;
import java.util.LinkedHashSet;
import java.util.Set;
import io.seata.core.model.GlobalStatus;
import io.seata.core.model.TransactionManager;
import io.seata.tm.TransactionManagerHolder;
import io.seata.tm.api.transaction.NoRollbackRule;
import io.seata.tm.api.transaction.RollbackRule;
import io.seata.tm.api.transaction.TransactionHook;
import io.seata.tm.api.transaction.TransactionHookManager;
import io.seata.tm.api.transaction.TransactionInfo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author guoyao
*/
public class TransactionTemplateTest {
private static final String DEFAULT_XID = "123456789";
private static final String DEFAULT_NAME = "test";
private static final int DEFAULT_TIME_OUT = 30000;
private TransactionalExecutor transactionalExecutor;
@BeforeEach
public void init() throws Exception {
// mock transactionManager
TransactionManager transactionManager = mock(TransactionManager.class);
when(transactionManager.begin(null, null, DEFAULT_NAME, DEFAULT_TIME_OUT)).thenReturn(DEFAULT_XID);
when(transactionManager.commit(DEFAULT_XID)).thenReturn(GlobalStatus.Committed);
when(transactionManager.rollback(DEFAULT_XID)).thenReturn(GlobalStatus.Rollbacked);
when(transactionManager.getStatus(DEFAULT_XID)).thenReturn(GlobalStatus.Begin);
TransactionManagerHolder.set(transactionManager);
//mock transactionalExecutor
transactionalExecutor = Mockito.mock(TransactionalExecutor.class);
TransactionInfo txInfo = new TransactionInfo();
txInfo.setTimeOut(DEFAULT_TIME_OUT);
txInfo.setName(DEFAULT_NAME);
when(transactionalExecutor.getTransactionInfo()).thenReturn(txInfo);
}
@AfterEach
public void assertHooks() {
assertThat(TransactionHookManager.getHooks()).isEmpty();
}
@Test
public void testTransactionCommitHook() throws Throwable {
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
TransactionHookManager.registerHook(transactionHook);
TransactionalTemplate template = new TransactionalTemplate();
template.execute(transactionalExecutor);
verifyCommit(transactionHook);
}
@Test
public void testTransactionRollbackHook() throws Throwable {
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
when(transactionalExecutor.execute()).thenThrow(new RuntimeException());
TransactionHookManager.registerHook(transactionHook);
TransactionalTemplate template = new TransactionalTemplate();
try {
template.execute(transactionalExecutor);
} catch (Exception e) {
//catch rollback exception
}
verifyRollBack(transactionHook);
}
@Test
public void testTransactionRollbackHook_WithRollBackRule() throws Throwable {
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
rollbackRules.add(new RollbackRule(NullPointerException.class));
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
verifyRollBack(transactionHook);
}
@Test
public void testTransactionRollbackHook_WithNoRollBackRule() throws Throwable {
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
rollbackRules.add(new NoRollbackRule(NullPointerException.class));
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
verifyCommit(transactionHook);
}
@Test
public void testTransactionRollbackHook_WithSameYesNoRollBackRule() throws Throwable {
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
rollbackRules.add(new RollbackRule(NullPointerException.class));
rollbackRules.add(new NoRollbackRule(NullPointerException.class));
TransactionHook transactionHook = testRollBackRules(rollbackRules, new NullPointerException());
verifyRollBack(transactionHook);
}
private TransactionHook testRollBackRules(Set<RollbackRule> rollbackRules, Throwable throwable) throws Throwable {
TransactionHook transactionHook = Mockito.mock(TransactionHook.class);
// mock txInfo
TransactionInfo txInfo = new TransactionInfo();
txInfo.setTimeOut(DEFAULT_TIME_OUT);
txInfo.setName(DEFAULT_NAME);
txInfo.setRollbackRules(rollbackRules);
when(transactionalExecutor.getTransactionInfo()).thenReturn(txInfo);
when(transactionalExecutor.execute()).thenThrow(throwable);
TransactionHookManager.registerHook(transactionHook);
TransactionalTemplate template = new TransactionalTemplate();
try {
template.execute(transactionalExecutor);
} catch (Exception e) {
//catch rollback exception
}
return transactionHook;
}
private void verifyCommit(TransactionHook transactionHook) {
verify(transactionHook).beforeBegin();
verify(transactionHook).afterBegin();
verify(transactionHook).beforeCommit();
verify(transactionHook).afterCommit();
verify(transactionHook).afterCompletion();
}
private void verifyRollBack(TransactionHook transactionHook) {
verify(transactionHook).beforeBegin();
verify(transactionHook).afterBegin();
verify(transactionHook).beforeRollback();
verify(transactionHook).afterRollback();
verify(transactionHook).afterCompletion();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
/**
* @author guoyao
*/
public class MyRuntimeException extends RuntimeException {
public MyRuntimeException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author l81893521
*/
public class NoRollbackRuleTest {
@Test
public void equalsTest(){
RollbackRule rollbackRuleByClass = new NoRollbackRule(Exception.class);
RollbackRule otherRollbackRuleByClass = new NoRollbackRule(Exception.class);
Assertions.assertEquals(rollbackRuleByClass, otherRollbackRuleByClass);
RollbackRule rollbackRuleByName = new NoRollbackRule(Exception.class.getName());
RollbackRule otherRollbackRuleByName = new NoRollbackRule(Exception.class.getName());
Assertions.assertEquals(rollbackRuleByName, otherRollbackRuleByName);
NoRollbackRule otherRollbackRuleByName3 = new NoRollbackRule(Exception.class.getName());
Assertions.assertEquals(otherRollbackRuleByName3.toString(),"NoRollbackRule with pattern [" + Exception.class.getName() + "]");
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import io.seata.common.exception.ShouldNeverHappenException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author guoyao
*/
public class RollbackRuleTest {
@Test
public void foundImmediatelyWithString() {
RollbackRule rr = new RollbackRule(Exception.class.getName());
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
}
@Test
public void RollbackRule() {
RollbackRule rr = new RollbackRule(Exception.class);
assertThat(rr.getDepth(new Exception())).isEqualTo(0);
}
@Test
public void notFound() {
RollbackRule rr = new RollbackRule(java.io.IOException.class.getName());
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(-1);
}
@Test
public void ancestry() {
RollbackRule rr = new RollbackRule(java.lang.Exception.class.getName());
// Exception -> Runtime -> MyRuntimeException
assertThat(rr.getDepth(new MyRuntimeException(""))).isEqualTo(2);
}
@Test
public void alwaysTrueForThrowable() {
RollbackRule rr = new RollbackRule(java.lang.Throwable.class.getName());
assertThat(rr.getDepth(new MyRuntimeException("")) > 0).isTrue();
assertThat(rr.getDepth(new IOException()) > 0).isTrue();
assertThat(rr.getDepth(new ShouldNeverHappenException(null, null)) > 0).isTrue();
assertThat(rr.getDepth(new RuntimeException()) > 0).isTrue();
}
@Test
public void ctorArgMustBeAThrowableClassWithNonThrowableType() {
Assertions.assertThrows(IllegalArgumentException.class, () -> new RollbackRule(String.class));
}
@Test
public void ctorArgMustBeAThrowableClassWithNullThrowableType() {
Assertions.assertThrows(NullPointerException.class, () -> new RollbackRule((Class<?>) null));
}
@Test
public void ctorArgExceptionStringNameVersionWithNull() {
Assertions.assertThrows(IllegalArgumentException.class, () -> new RollbackRule((String) null));
}
@Test
public void toStringTest(){
RollbackRule otherRollbackRuleByName = new RollbackRule(Exception.class.getName());
Assertions.assertEquals(otherRollbackRuleByName.toString(), String.format("RollbackRule with pattern [%s]", Exception.class.getName()));
}
@Test
public void equalsTest(){
RollbackRule otherRollbackRuleByName = new RollbackRule(Exception.class.getName());
RollbackRule otherRollbackRuleByName2 = new NoRollbackRule(Exception.class.getName());
Assertions.assertNotEquals("", otherRollbackRuleByName);
Assertions.assertEquals(otherRollbackRuleByName, otherRollbackRuleByName);
Assertions.assertEquals(otherRollbackRuleByName, otherRollbackRuleByName2);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author guoyao
*/
public class TransactionHookManagerTest {
@AfterEach
public void clear() {
TransactionHookManager.clear();
}
@Test
public void testRegisterHook() {
TransactionHookAdapter transactionHookAdapter = new TransactionHookAdapter();
TransactionHookManager.registerHook(transactionHookAdapter);
List<TransactionHook> hooks = TransactionHookManager.getHooks();
assertThat(hooks).isNotEmpty();
assertThat(hooks.get(0)).isEqualTo(transactionHookAdapter);
}
@Test
public void testGetHooks() {
assertThat(TransactionHookManager.getHooks()).isEmpty();
TransactionHookManager.registerHook(new TransactionHookAdapter());
assertThat(TransactionHookManager.getHooks()).isNotEmpty();
}
@Test
public void testClear() {
assertThat(TransactionHookManager.getHooks()).isEmpty();
TransactionHookManager.registerHook(new TransactionHookAdapter());
assertThat(TransactionHookManager.getHooks()).isNotEmpty();
TransactionHookManager.clear();
assertThat(TransactionHookManager.getHooks()).isEmpty();
}
@Test
public void testNPE() {
Assertions.assertThrows(NullPointerException.class, () -> TransactionHookManager.registerHook(null));
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* 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 io.seata.tm.api.transaction;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author guoyao
*/
public class TransactionInfoTest {
private static final String IO_EXCEPTION_SHORT_NAME = "IOException";
@Test
public void testRollBackOn() {
TransactionInfo txInfo = new TransactionInfo();
//default true
assertThat(txInfo.rollbackOn(new IllegalArgumentException())).isTrue();
assertThat(txInfo.rollbackOn(new Exception())).isTrue();
assertThat(txInfo.rollbackOn(new IOException())).isTrue();
assertThat(txInfo.rollbackOn(new NullPointerException())).isTrue();
Set<RollbackRule> sets = getRollbackRules();
txInfo.setRollbackRules(sets);
assertThat(txInfo.rollbackOn(new IllegalArgumentException())).isTrue();
assertThat(txInfo.rollbackOn(new IllegalStateException())).isTrue();
assertThat(txInfo.rollbackOn(new IOException())).isFalse();
assertThat(txInfo.rollbackOn(new NullPointerException())).isFalse();
// not found return false
assertThat(txInfo.rollbackOn(new MyRuntimeException("test"))).isFalse();
assertThat(txInfo.rollbackOn(new RuntimeException())).isFalse();
assertThat(txInfo.rollbackOn(new Throwable())).isFalse();
}
private Set<RollbackRule> getRollbackRules() {
Set<RollbackRule> sets = new LinkedHashSet<>();
sets.add(new RollbackRule(IllegalStateException.class.getName()));
sets.add(new RollbackRule(IllegalArgumentException.class));
sets.add(new NoRollbackRule(IO_EXCEPTION_SHORT_NAME));
sets.add(new NoRollbackRule(NullPointerException.class));
return sets;
}
}