This is an automated email from the ASF dual-hosted git repository.

jimin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 65610d0e8e optimize: compatible with integration-tx-api module and 
spring module (#6342)
65610d0e8e is described below

commit 65610d0e8ea6f5bf713745f43719382c2d8e8402
Author: xingfudeshi <[email protected]>
AuthorDate: Fri Feb 16 22:58:38 2024 +0800

    optimize: compatible with integration-tx-api module and spring module 
(#6342)
---
 changes/en-us/2.0.0.md                             |   1 +
 changes/zh-cn/2.0.0.md                             |   2 +
 compatible/pom.xml                                 |   6 +
 .../java/io/seata/common/LockStrategyMode.java     |  28 ++++
 .../tx/api/interceptor/ActionContextUtil.java      | 170 +++++++++++++++++++++
 .../api/interceptor/ActionInterceptorHandler.java  |  67 ++++++++
 .../GlobalTransactionalInterceptorHandler.java     |  95 ++++++++++++
 .../GlobalTransactionalInterceptorParser.java      |  62 ++++++++
 .../io/seata/rm/tcc/api/BusinessActionContext.java |  20 +++
 .../rm/tcc/api/BusinessActionContextParameter.java |  70 +++++++++
 .../io/seata/spring/annotation/GlobalLock.java     |  47 ++++++
 .../spring/annotation/GlobalTransactional.java     | 121 +++++++++++++++
 .../main/java/io/seata/tm/api/FailureHandler.java  |  20 +++
 .../io/seata/tm/api/transaction/Propagation.java   | 168 ++++++++++++++++++++
 .../tx/api/interceptor/ActionContextUtil.java      |  18 +--
 .../GlobalTransactionalInterceptorHandler.java     |  36 ++---
 .../GlobalTransactionalInterceptorParser.java      |   4 +-
 17 files changed, 906 insertions(+), 29 deletions(-)

diff --git a/changes/en-us/2.0.0.md b/changes/en-us/2.0.0.md
index 7b8757be94..015ecfac60 100644
--- a/changes/en-us/2.0.0.md
+++ b/changes/en-us/2.0.0.md
@@ -155,6 +155,7 @@ The version is updated as follows:
 - [[#5959](https://github.com/seata/seata/pull/5959)] modify code style and 
remove unused import
 - [[#6002](https://github.com/seata/seata/pull/6002)] remove fst serialization
 - [[#6045](https://github.com/seata/seata/pull/6045)] optimize derivative 
product check base on mysql
+- [[#6342](https://github.com/seata/seata/pull/6342)] compatible with 
integration-tx-api module
 
 ### security:
 - [[#5642](https://github.com/seata/seata/pull/5642)] add Hessian Serializer 
WhiteDenyList
diff --git a/changes/zh-cn/2.0.0.md b/changes/zh-cn/2.0.0.md
index 0855991544..0982e53a3b 100644
--- a/changes/zh-cn/2.0.0.md
+++ b/changes/zh-cn/2.0.0.md
@@ -156,6 +156,8 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单
 - [[#5959](https://github.com/seata/seata/pull/5959)] 修正代码风格问题及去除无用的类引用
 - [[#6002](https://github.com/seata/seata/pull/6002)] 移除fst序列化模块
 - [[#6045](https://github.com/seata/seata/pull/6045)] 优化MySQL衍生数据库判断逻辑
+- [[#6342](https://github.com/seata/seata/pull/6342)] 兼容integration-tx-api模块
+
 
 
 ### security:
diff --git a/compatible/pom.xml b/compatible/pom.xml
index 865aa264f8..bf2d301b00 100644
--- a/compatible/pom.xml
+++ b/compatible/pom.xml
@@ -86,6 +86,12 @@
             <artifactId>seata-rm-datasource</artifactId>
             <version>2.1.0-SNAPSHOT</version>
             <scope>compile</scope>
+        </dependency>
+              <dependency>
+            <groupId>org.apache.seata</groupId>
+            <artifactId>seata-integration-tx-api</artifactId>
+            <version>2.1.0-SNAPSHOT</version>
+            <scope>compile</scope>
         </dependency>
     </dependencies>
 </project>
diff --git a/compatible/src/main/java/io/seata/common/LockStrategyMode.java 
b/compatible/src/main/java/io/seata/common/LockStrategyMode.java
new file mode 100644
index 0000000000..530c95dded
--- /dev/null
+++ b/compatible/src/main/java/io/seata/common/LockStrategyMode.java
@@ -0,0 +1,28 @@
+/*
+ * 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 io.seata.common;
+
+public enum LockStrategyMode {
+    /**
+     * Optimistic lock mode is recommended when resources are not reused in 
the current global transaction.
+     */
+    OPTIMISTIC,
+    /**
+     * Pessimistic lock mode is recommended when there may be repeated use of 
the same resource in a global transaction.
+     */
+    PESSIMISTIC
+}
diff --git 
a/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionContextUtil.java
 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionContextUtil.java
new file mode 100644
index 0000000000..ac90af80be
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionContextUtil.java
@@ -0,0 +1,170 @@
+/*
+ * 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 io.seata.integration.tx.api.interceptor;
+
+import io.seata.rm.tcc.api.BusinessActionContext;
+import io.seata.rm.tcc.api.BusinessActionContextParameter;
+import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.rm.tcc.api.ParamType;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Map;
+
+import static 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.getByIndex;
+
+/**
+ * Extracting TCC Context from Method
+ */
+public final class ActionContextUtil {
+
+    private ActionContextUtil() {
+    }
+
+    /**
+     * Extracting context data from parameters
+     *
+     * @param targetParam the target param
+     * @return map the context
+     */
+    public static Map<String, Object> fetchContextFromObject(@Nonnull Object 
targetParam) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.fetchContextFromObject(targetParam);
+    }
+
+    /**
+     * load param by the config of annotation, and then put into the action 
context
+     *
+     * @param paramType     the param type, 'param' or 'field'
+     * @param paramName     the param name
+     * @param paramValue    the param value
+     * @param annotation    the annotation on the param or field
+     * @param actionContext the action context
+     */
+    public static void loadParamByAnnotationAndPutToContext(@Nonnull final 
ParamType paramType, @Nonnull String paramName, Object paramValue,
+                                                            @Nonnull final 
BusinessActionContextParameter annotation, @Nonnull final Map<String, Object> 
actionContext) {
+        if (paramValue == null) {
+            return;
+        }
+
+        // If {@code index >= 0}, get by index from the list param or field
+        int index = annotation.index();
+        if (index >= 0) {
+            paramValue = getByIndex(paramType, paramName, paramValue, index);
+            if (paramValue == null) {
+                return;
+            }
+        }
+
+        // if {@code isParamInProperty == true}, fetch context from paramValue
+        if (annotation.isParamInProperty()) {
+            Map<String, Object> paramContext = 
fetchContextFromObject(paramValue);
+            if (CollectionUtils.isNotEmpty(paramContext)) {
+                actionContext.putAll(paramContext);
+            }
+        } else {
+            // get param name from the annotation
+            String paramNameFromAnnotation = 
getParamNameFromAnnotation(annotation);
+            if (StringUtils.isNotBlank(paramNameFromAnnotation)) {
+                paramName = paramNameFromAnnotation;
+            }
+            putActionContextWithoutHandle(actionContext, paramName, 
paramValue);
+        }
+    }
+
+
+    public static String getParamNameFromAnnotation(@Nonnull 
BusinessActionContextParameter annotation) {
+        String paramName = annotation.paramName();
+        if (StringUtils.isBlank(paramName)) {
+            paramName = annotation.value();
+        }
+        return paramName;
+    }
+
+    /**
+     * put the action context after handle
+     *
+     * @param actionContext the action context
+     * @param key           the actionContext's key
+     * @param value         the actionContext's value
+     * @return the action context is changed
+     */
+    public static boolean putActionContext(Map<String, Object> actionContext, 
String key, Object value) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.putActionContext(actionContext,
 key, value);
+    }
+
+    /**
+     * put the action context after handle
+     *
+     * @param actionContext    the action context
+     * @param actionContextMap the actionContextMap
+     * @return the action context is changed
+     */
+    public static boolean putActionContext(Map<String, Object> actionContext, 
@Nonnull Map<String, Object> actionContextMap) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.putActionContext(actionContext,
 actionContextMap);
+    }
+
+    /**
+     * put the action context without handle
+     *
+     * @param actionContext the action context
+     * @param key           the actionContext's key
+     * @param value         the actionContext's value
+     * @return the action context is changed
+     */
+    public static boolean putActionContextWithoutHandle(@Nonnull final 
Map<String, Object> actionContext, String key, Object value) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.putActionContextWithoutHandle(actionContext,
 key, value);
+    }
+
+    /**
+     * put the action context without handle
+     *
+     * @param actionContext    the action context
+     * @param actionContextMap the actionContextMap
+     * @return the action context is changed
+     */
+    public static boolean putActionContextWithoutHandle(Map<String, Object> 
actionContext, @Nonnull Map<String, Object> actionContextMap) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.putActionContextWithoutHandle(actionContext,
 actionContextMap);
+    }
+
+    /**
+     * Handle the action context.
+     * It is convenient to convert type in phase 2.
+     *
+     * @param actionContext the action context
+     * @return the action context or JSON string
+     * @see #convertActionContext(String, Object, Class)
+     * @see BusinessActionContext#getActionContext(String, Class)
+     */
+    public static Object handleActionContext(@Nonnull Object actionContext) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.handleActionContext(actionContext);
+    }
+
+    /**
+     * Convert action context
+     *
+     * @param key         the actionContext's key
+     * @param value       the actionContext's value
+     * @param targetClazz the target class
+     * @param <T>         the target type
+     * @return the action context of the target type
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T convertActionContext(String key, @Nullable Object 
value, @Nonnull Class<T> targetClazz) {
+        return 
org.apache.seata.integration.tx.api.interceptor.ActionContextUtil.convertActionContext(key,
 value, targetClazz);
+    }
+}
diff --git 
a/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
new file mode 100644
index 0000000000..161188cd03
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/ActionInterceptorHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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 io.seata.integration.tx.api.interceptor;
+
+import io.seata.rm.tcc.api.BusinessActionContextParameter;
+import org.apache.seata.rm.tcc.api.ParamType;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handler the Tx Participant Aspect : Setting Context, Creating Branch Record
+ */
+public class ActionInterceptorHandler extends 
org.apache.seata.integration.tx.api.interceptor.ActionInterceptorHandler {
+
+
+    /**
+     * Extracting context data from parameters, add them to the context
+     *
+     * @param method    the method
+     * @param arguments the arguments
+     * @return the context
+     */
+    @Override
+    protected Map<String, Object> fetchActionRequestContext(Method method, 
Object[] arguments) {
+        Map<String, Object> context = new HashMap<>(8);
+
+        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+        for (int i = 0; i < parameterAnnotations.length; i++) {
+            for (int j = 0; j < parameterAnnotations[i].length; j++) {
+                if (parameterAnnotations[i][j] instanceof 
org.apache.seata.rm.tcc.api.BusinessActionContextParameter) {
+                    // get annotation
+                    BusinessActionContextParameter annotation = 
(BusinessActionContextParameter) parameterAnnotations[i][j];
+                    if (arguments[i] == null) {
+                        throw new 
IllegalArgumentException("@BusinessActionContextParameter 's params can not 
null");
+                    }
+
+                    // get param
+                    Object paramObject = arguments[i];
+                    if (paramObject == null) {
+                        continue;
+                    }
+
+                    // load param by the config of annotation, and then put 
into the context
+                    
ActionContextUtil.loadParamByAnnotationAndPutToContext(ParamType.PARAM, "", 
paramObject, annotation, context);
+                }
+            }
+        }
+        return context;
+    }
+}
diff --git 
a/compatible/src/main/java/io/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
new file mode 100644
index 0000000000..2882ec1fa9
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
@@ -0,0 +1,95 @@
+/*
+ * 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 io.seata.integration.tx.api.interceptor.handler;
+
+import io.seata.spring.annotation.GlobalLock;
+import io.seata.spring.annotation.GlobalTransactional;
+import io.seata.tm.api.FailureHandler;
+import org.apache.seata.core.model.GlobalLockConfig;
+import org.apache.seata.integration.tx.api.annotation.AspectTransactional;
+import org.apache.seata.integration.tx.api.interceptor.InvocationWrapper;
+import org.apache.seata.integration.tx.api.util.ClassUtils;
+import org.apache.seata.rm.GlobalLockExecutor;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+
+/**
+ * The type Global transactional interceptor handler.
+ */
+public class GlobalTransactionalInterceptorHandler extends 
org.apache.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler
 {
+
+
+    public GlobalTransactionalInterceptorHandler(FailureHandler 
failureHandler, Set<String> methodsToProxy) {
+        super(failureHandler, methodsToProxy);
+    }
+
+    public GlobalTransactionalInterceptorHandler(FailureHandler 
failureHandler, Set<String> methodsToProxy, AspectTransactional 
aspectTransactional) {
+        super(failureHandler, methodsToProxy, aspectTransactional);
+    }
+
+    @Override
+    protected Object doInvoke(InvocationWrapper invocation) throws Throwable {
+        Class<?> targetClass = invocation.getTarget().getClass();
+        Method specificMethod = 
ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
+        if (specificMethod != null && 
!specificMethod.getDeclaringClass().equals(Object.class)) {
+            final GlobalTransactional globalTransactionalAnnotation = 
getAnnotation(specificMethod, targetClass, GlobalTransactional.class);
+            final GlobalLock globalLockAnnotation = 
getAnnotation(specificMethod, targetClass, GlobalLock.class);
+            boolean localDisable = disable || (ATOMIC_DEGRADE_CHECK.get() && 
degradeNum >= degradeCheckAllowTimes);
+            if (!localDisable) {
+                if (globalTransactionalAnnotation != null || 
this.aspectTransactional != null) {
+                    AspectTransactional transactional;
+                    if (globalTransactionalAnnotation != null) {
+                        transactional = new 
AspectTransactional(globalTransactionalAnnotation.timeoutMills(),
+                                globalTransactionalAnnotation.name(), 
globalTransactionalAnnotation.rollbackFor(),
+                                
globalTransactionalAnnotation.rollbackForClassName(),
+                                globalTransactionalAnnotation.noRollbackFor(),
+                                
globalTransactionalAnnotation.noRollbackForClassName(),
+                                
org.apache.seata.tm.api.transaction.Propagation.valueOf(globalTransactionalAnnotation.propagation().name()),
+                                
globalTransactionalAnnotation.lockRetryInterval(),
+                                globalTransactionalAnnotation.lockRetryTimes(),
+                                
org.apache.seata.common.LockStrategyMode.valueOf(globalTransactionalAnnotation.lockStrategyMode().name()));
+                    } else {
+                        transactional = this.aspectTransactional;
+                    }
+                    return handleGlobalTransaction(invocation, transactional);
+                } else if (globalLockAnnotation != null) {
+                    return handleGlobalLock(invocation, globalLockAnnotation);
+                }
+            }
+        }
+        return invocation.proceed();
+    }
+
+
+    private Object handleGlobalLock(final InvocationWrapper methodInvocation, 
final GlobalLock globalLockAnno) throws Throwable {
+        return globalLockTemplate.execute(new GlobalLockExecutor() {
+            @Override
+            public Object execute() throws Throwable {
+                return methodInvocation.proceed();
+            }
+
+            @Override
+            public GlobalLockConfig getGlobalLockConfig() {
+                GlobalLockConfig config = new GlobalLockConfig();
+                
config.setLockRetryInterval(globalLockAnno.lockRetryInterval());
+                config.setLockRetryTimes(globalLockAnno.lockRetryTimes());
+                return config;
+            }
+        });
+    }
+}
diff --git 
a/compatible/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
new file mode 100644
index 0000000000..485034e6e3
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
@@ -0,0 +1,62 @@
+/*
+ * 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 io.seata.integration.tx.api.interceptor.parser;
+
+import io.seata.spring.annotation.GlobalLock;
+import io.seata.spring.annotation.GlobalTransactional;
+import org.apache.seata.common.util.CollectionUtils;
+
+import java.lang.reflect.Method;
+
+public class GlobalTransactionalInterceptorParser extends 
org.apache.seata.integration.tx.api.interceptor.parser.GlobalTransactionalInterceptorParser
 {
+
+    @Override
+    protected boolean existsAnnotation(Class<?>... classes) {
+        boolean result = false;
+        if (CollectionUtils.isNotEmpty(classes)) {
+            for (Class<?> clazz : classes) {
+                if (clazz == null) {
+                    continue;
+                }
+                GlobalTransactional trxAnnoOld = 
clazz.getAnnotation(GlobalTransactional.class);
+                org.apache.seata.spring.annotation.GlobalTransactional 
trxAnnoNew = 
clazz.getAnnotation(org.apache.seata.spring.annotation.GlobalTransactional.class);
+
+                if (trxAnnoOld != null || trxAnnoNew != null) {
+                    return true;
+                }
+                Method[] methods = clazz.getMethods();
+                for (Method method : methods) {
+                    trxAnnoOld = 
method.getAnnotation(GlobalTransactional.class);
+                    trxAnnoNew = 
method.getAnnotation(org.apache.seata.spring.annotation.GlobalTransactional.class);
+                    if (trxAnnoOld != null || trxAnnoNew != null) {
+                        methodsToProxy.add(method.getName());
+                        result = true;
+                    }
+
+                    GlobalLock lockAnnoOld = 
method.getAnnotation(GlobalLock.class);
+                    org.apache.seata.spring.annotation.GlobalLock lockAnnoNew 
= method.getAnnotation(org.apache.seata.spring.annotation.GlobalLock.class);
+
+                    if (lockAnnoOld != null || lockAnnoNew != null) {
+                        methodsToProxy.add(method.getName());
+                        result = true;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git 
a/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java 
b/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java
new file mode 100644
index 0000000000..d3ad5cd05b
--- /dev/null
+++ b/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContext.java
@@ -0,0 +1,20 @@
+/*
+ * 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 io.seata.rm.tcc.api;
+
+public class BusinessActionContext extends 
org.apache.seata.rm.tcc.api.BusinessActionContext {
+}
diff --git 
a/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java
 
b/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java
new file mode 100644
index 0000000000..d9649587ef
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/rm/tcc/api/BusinessActionContextParameter.java
@@ -0,0 +1,70 @@
+/*
+ * 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 io.seata.rm.tcc.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD})
+public @interface BusinessActionContextParameter {
+
+    /**
+     * parameter's name. Synonym for {@link #paramName()}.
+     *
+     * @return the name of the param or field
+     * @see 
org.apache.seata.spring.interceptor.ActionContextUtil#getParamNameFromAnnotation
+     */
+    String value() default "";
+
+    /**
+     * parameter's name. Synonym for {@link #value()}.
+     *
+     * @return the name of the param or field
+     * @see 
org.apache.seata.spring.interceptor.ActionContextUtil#getParamNameFromAnnotation
+     */
+    String paramName() default "";
+
+    /**
+     * if it is a sharding param ?
+     *
+     * @return the boolean
+     * @deprecated This property is no longer in use.
+     */
+    @Deprecated
+    boolean isShardingParam() default false;
+
+    /**
+     * Specify the index of the parameter in the List
+     *
+     * @return the index of the List
+     * @see org.apache.seata.spring.interceptor.ActionContextUtil#getByIndex
+     */
+    int index() default -1;
+
+    /**
+     * whether get the parameter from the property of the object
+     * if {@code index >= 0}, the object get from the List and then do get the 
parameter from the property of the object
+     *
+     * @return the boolean
+     * @see 
org.apache.seata.spring.interceptor.ActionContextUtil#loadParamByAnnotationAndPutToContext
+     * @see 
org.apache.seata.spring.interceptor.ActionContextUtil#fetchContextFromObject
+     */
+    boolean isParamInProperty() default false;
+}
diff --git 
a/compatible/src/main/java/io/seata/spring/annotation/GlobalLock.java 
b/compatible/src/main/java/io/seata/spring/annotation/GlobalLock.java
new file mode 100644
index 0000000000..f47e32976a
--- /dev/null
+++ b/compatible/src/main/java/io/seata/spring/annotation/GlobalLock.java
@@ -0,0 +1,47 @@
+/*
+ * 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 io.seata.spring.annotation;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Inherited
+public @interface GlobalLock {
+    /**
+     * customized global lock retry interval(unit: ms)
+     * you may use this to override global config of 
"client.rm.lock.retryInterval"
+     * note: 0 or negative number will take no effect(which mean fall back to 
global config)
+     *
+     * @return lock retry interval
+     */
+    int lockRetryInterval() default 0;
+
+    /**
+     * customized global lock retry times
+     * you may use this to override global config of 
"client.rm.lock.retryTimes"
+     * note: negative number will take no effect(which mean fall back to 
global config)
+     *
+     * @return lock retry times
+     */
+    int lockRetryTimes() default -1;
+}
diff --git 
a/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactional.java 
b/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactional.java
new file mode 100644
index 0000000000..f4d4fe0818
--- /dev/null
+++ 
b/compatible/src/main/java/io/seata/spring/annotation/GlobalTransactional.java
@@ -0,0 +1,121 @@
+/*
+ * 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 io.seata.spring.annotation;
+
+import io.seata.common.LockStrategyMode;
+import io.seata.tm.api.transaction.Propagation;
+import org.apache.seata.common.DefaultValues;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Inherited
+public @interface GlobalTransactional {
+
+    /**
+     * Global transaction timeoutMills in MILLISECONDS.
+     * If client.tm.default-global-transaction-timeout is configured, It will 
replace the DefaultValues
+     * .DEFAULT_GLOBAL_TRANSACTION_TIMEOUT.
+     *
+     * @return timeoutMills in MILLISECONDS.
+     */
+    int timeoutMills() default 
DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
+
+    /**
+     * Given name of the global transaction instance.
+     *
+     * @return Given name.
+     */
+    String name() default "";
+
+    /**
+     * roll back for the Class
+     *
+     * @return the class array of the rollback for
+     */
+    Class<? extends Throwable>[] rollbackFor() default {};
+
+    /**
+     * roll back for the class name
+     *
+     * @return the class name of rollback for
+     */
+    String[] rollbackForClassName() default {};
+
+    /**
+     * not roll back for the Class
+     *
+     * @return the class array of no rollback for
+     */
+    Class<? extends Throwable>[] noRollbackFor() default {};
+
+    /**
+     * not roll back for the class name
+     *
+     * @return string [ ]
+     */
+    String[] noRollbackForClassName() default {};
+
+    /**
+     * the propagation of the global transaction
+     *
+     * @return propagation
+     */
+    Propagation propagation() default Propagation.REQUIRED;
+
+    /**
+     * customized global lock retry interval(unit: ms)
+     * you may use this to override global config of 
"client.rm.lock.retryInterval"
+     * note: 0 or negative number will take no effect(which mean fall back to 
global config)
+     *
+     * @return int
+     */
+    int lockRetryInterval() default 0;
+
+    /**
+     * customized global lock retry interval(unit: ms)
+     * you may use this to override global config of 
"client.rm.lock.retryInterval"
+     * note: 0 or negative number will take no effect(which mean fall back to 
global config)
+     *
+     * @return int
+     */
+    @Deprecated
+    int lockRetryInternal() default 0;
+
+    /**
+     * customized global lock retry times
+     * you may use this to override global config of 
"client.rm.lock.retryTimes"
+     * note: negative number will take no effect(which mean fall back to 
global config)
+     *
+     * @return int
+     */
+    int lockRetryTimes() default -1;
+
+    /**
+     * pick the Acquire lock policy
+     *
+     * @return lock strategy mode
+     */
+    LockStrategyMode lockStrategyMode() default LockStrategyMode.PESSIMISTIC;
+
+}
diff --git a/compatible/src/main/java/io/seata/tm/api/FailureHandler.java 
b/compatible/src/main/java/io/seata/tm/api/FailureHandler.java
new file mode 100644
index 0000000000..5ff6615201
--- /dev/null
+++ b/compatible/src/main/java/io/seata/tm/api/FailureHandler.java
@@ -0,0 +1,20 @@
+/*
+ * 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 io.seata.tm.api;
+
+public interface FailureHandler extends org.apache.seata.tm.api.FailureHandler 
{
+}
diff --git 
a/compatible/src/main/java/io/seata/tm/api/transaction/Propagation.java 
b/compatible/src/main/java/io/seata/tm/api/transaction/Propagation.java
new file mode 100644
index 0000000000..feae3a3cd8
--- /dev/null
+++ b/compatible/src/main/java/io/seata/tm/api/transaction/Propagation.java
@@ -0,0 +1,168 @@
+/*
+ * 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 io.seata.tm.api.transaction;
+
+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
+}
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtil.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtil.java
index 08e2459fec..ad6b7be469 100644
--- 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtil.java
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtil.java
@@ -16,14 +16,6 @@
  */
 package org.apache.seata.integration.tx.api.interceptor;
 
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.lang.reflect.Field;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 import org.apache.seata.common.exception.FrameworkException;
 import org.apache.seata.common.util.CollectionUtils;
 import org.apache.seata.common.util.ReflectionUtil;
@@ -35,6 +27,14 @@ import org.apache.seata.rm.tcc.api.ParamType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Extracting TCC Context from Method
  *
@@ -128,7 +128,7 @@ public final class ActionContextUtil {
     }
 
     @Nullable
-    private static Object getByIndex(@Nonnull ParamType paramType, @Nonnull 
String paramName, @Nonnull Object paramValue, int index) {
+    public static Object getByIndex(@Nonnull ParamType paramType, @Nonnull 
String paramName, @Nonnull Object paramValue, int index) {
         if (paramValue instanceof List) {
             @SuppressWarnings("unchecked")
             List<Object> list = (List<Object>)paramValue;
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
index 7f9750a646..bc3a401402 100644
--- 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/handler/GlobalTransactionalInterceptorHandler.java
@@ -16,16 +16,6 @@
  */
 package org.apache.seata.integration.tx.api.interceptor.handler;
 
-import org.apache.seata.tm.api.GlobalTransaction;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.LinkedHashSet;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import org.apache.seata.common.exception.ShouldNeverHappenException;
 import org.apache.seata.common.thread.NamedThreadFactory;
 import org.apache.seata.common.util.StringUtils;
@@ -51,6 +41,7 @@ import org.apache.seata.spring.annotation.GlobalTransactional;
 import org.apache.seata.tm.TransactionManagerHolder;
 import org.apache.seata.tm.api.FailureHandler;
 import org.apache.seata.tm.api.FailureHandlerHolder;
+import org.apache.seata.tm.api.GlobalTransaction;
 import org.apache.seata.tm.api.TransactionalExecutor;
 import org.apache.seata.tm.api.TransactionalTemplate;
 import org.apache.seata.tm.api.transaction.NoRollbackRule;
@@ -59,6 +50,15 @@ import org.apache.seata.tm.api.transaction.TransactionInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import static 
org.apache.seata.common.DefaultValues.DEFAULT_DISABLE_GLOBAL_TRANSACTION;
 import static 
org.apache.seata.common.DefaultValues.DEFAULT_GLOBAL_TRANSACTION_TIMEOUT;
 import static org.apache.seata.common.DefaultValues.DEFAULT_TM_DEGRADE_CHECK;
@@ -75,15 +75,15 @@ public class GlobalTransactionalInterceptorHandler extends 
AbstractProxyInvocati
     private static final Logger LOGGER = 
LoggerFactory.getLogger(GlobalTransactionalInterceptorHandler.class);
 
     private final TransactionalTemplate transactionalTemplate = new 
TransactionalTemplate();
-    private final GlobalLockTemplate globalLockTemplate = new 
GlobalLockTemplate();
+    protected final GlobalLockTemplate globalLockTemplate = new 
GlobalLockTemplate();
 
     private Set<String> methodsToProxy;
 
-    private volatile boolean disable;
-    private static final AtomicBoolean ATOMIC_DEGRADE_CHECK = new 
AtomicBoolean(false);
-    private static volatile Integer degradeNum = 0;
+    protected volatile boolean disable;
+    protected static final AtomicBoolean ATOMIC_DEGRADE_CHECK = new 
AtomicBoolean(false);
+    protected static volatile Integer degradeNum = 0;
     private static volatile Integer reachNum = 0;
-    private static int degradeCheckAllowTimes;
+    protected static int degradeCheckAllowTimes;
     protected AspectTransactional aspectTransactional;
     private static int degradeCheckPeriod;
 
@@ -172,7 +172,7 @@ public class GlobalTransactionalInterceptorHandler extends 
AbstractProxyInvocati
     }
 
 
-    private Object handleGlobalLock(final InvocationWrapper methodInvocation, 
final GlobalLock globalLockAnno) throws Throwable {
+    protected Object handleGlobalLock(final InvocationWrapper 
methodInvocation, final GlobalLock globalLockAnno) throws Throwable {
         return globalLockTemplate.execute(new GlobalLockExecutor() {
             @Override
             public Object execute() throws Throwable {
@@ -189,8 +189,8 @@ public class GlobalTransactionalInterceptorHandler extends 
AbstractProxyInvocati
         });
     }
 
-    Object handleGlobalTransaction(final InvocationWrapper methodInvocation,
-                                   final AspectTransactional 
aspectTransactional) throws Throwable {
+    protected Object handleGlobalTransaction(final InvocationWrapper 
methodInvocation,
+                                             final AspectTransactional 
aspectTransactional) throws Throwable {
         boolean succeed = true;
         try {
             return transactionalTemplate.execute(new TransactionalExecutor() {
diff --git 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
index 3026615db9..beacda7de1 100644
--- 
a/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
+++ 
b/integration-tx-api/src/main/java/org/apache/seata/integration/tx/api/interceptor/parser/GlobalTransactionalInterceptorParser.java
@@ -34,7 +34,7 @@ import org.apache.seata.tm.api.FailureHandlerHolder;
 
 public class GlobalTransactionalInterceptorParser implements InterfaceParser {
 
-    private final Set<String> methodsToProxy = new HashSet<>();
+    protected final Set<String> methodsToProxy = new HashSet<>();
 
     /**
      * @param target
@@ -72,7 +72,7 @@ public class GlobalTransactionalInterceptorParser implements 
InterfaceParser {
         return ifNeedEnhanceBean;
     }
 
-    private boolean existsAnnotation(Class<?>... classes) {
+    protected boolean existsAnnotation(Class<?>... classes) {
         boolean result = false;
         if (CollectionUtils.isNotEmpty(classes)) {
             for (Class<?> clazz : classes) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to